JakeWharton/retrofit2-kotlinx-serialization-converter

exclude class discriminator

P1NG2WIN opened this issue · 4 comments

Can I configure ConverterFactory to automatically exclude classDiscriminator when parsing an object to json?

Unfortunately, Kotlin/kotlinx.serialization#1247 has been hanging for half a year, so apparently you need to solve this case yourself.

Considering that the most common case where this is needed is when sending data to the server, I think the best workaround would be to remove the classDiscriminator in the retrofit parser.

My version

internal sealed class Serializer {
    abstract fun <T> fromResponseBody(loader: DeserializationStrategy<T>, body: ResponseBody): T
    abstract fun <T> toRequestBody(contentType: MediaType, saver: SerializationStrategy<T>, value: T): RequestBody

    @OptIn(ExperimentalSerializationApi::class) // Experimental is only for subtypes.
    protected abstract val format: SerialFormat

    @ExperimentalSerializationApi // serializer(Type) is not stable.
    fun serializer(type: Type): KSerializer<Any> = format.serializersModule.serializer(type)

    @OptIn(ExperimentalSerializationApi::class) // Experimental is only for subtypes.
    class FromString(
        override val format: StringFormat,
        private val excludeClassDiscriminator: Boolean
    ) : Serializer() {
        override fun <T> fromResponseBody(loader: DeserializationStrategy<T>, body: ResponseBody): T {
            val string = body.string()
            return format.decodeFromString(loader, string)
        }

        override fun <T> toRequestBody(contentType: MediaType, saver: SerializationStrategy<T>, value: T): RequestBody {
            val json = format as Json

            val string = when (excludeClassDiscriminator) {
                false -> format.encodeToString(saver, value)
                true -> {
                    val json1 = json.encodeToJsonElement(saver, value)

                    val valuesWithoutDiscriminator = tryOrNull { json1.filterJsonElement(json) }
                    json.encodeToString(valuesWithoutDiscriminator)
                }
            }

            return RequestBody.create(contentType, string)
        }
    }

    @OptIn(ExperimentalSerializationApi::class) // Experimental is only for subtypes.
    class FromBytes(override val format: BinaryFormat) : Serializer() {
        override fun <T> fromResponseBody(loader: DeserializationStrategy<T>, body: ResponseBody): T {
            val bytes = body.bytes()
            return format.decodeFromByteArray(loader, bytes)
        }

        override fun <T> toRequestBody(contentType: MediaType, saver: SerializationStrategy<T>, value: T): RequestBody {
            val bytes = format.encodeToByteArray(saver, value)
            return RequestBody.create(contentType, bytes)
        }
    }
}

fun JsonElement.filterJsonElement(json: Json): JsonElement {
    val classDiscriminator = json.configuration.classDiscriminator

    return when (this) {
        is JsonObject -> { JsonObject(
            jsonObject
                .filterNot { it.key == classDiscriminator }
                .mapValues { it.value.filterJsonElement(json) }
        ) }

        is JsonPrimitive -> {
            if (this.contentOrNull?.contains(classDiscriminator) == true)
                TODO()
            else
                this
        }

        JsonNull -> JsonNull

        is JsonArray -> {
            JsonArray(mapNotNull { it.filterJsonElement(json) })
        }
    }
}

Such functionality does not belong at this layer and I don't want to maintain overlay features. If the underlying library does not support it then there's nothing I can do. If you need this, your best bet is to fork this project and integrate your desired changes.

Such functionality does not belong at this layer and I don't want to maintain overlay features. If the underlying library does not support it then there's nothing I can do. If you need this, your best bet is to fork this project and integrate your desired changes.

Fully understand your position. Then, can you comment on my decision? Maybe you don't like something?

Generally it looks good and basically how I would also implement it. Go from model object to JSON DOM, manipulate DOM, go from JSON DOM to JSON string.

👍