Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.41k stars 619 forks source link

Skipping unknown elements for sealed class in Cbor/Protobuf/Json #2085

Open Coneys opened 2 years ago

Coneys commented 2 years ago

What is your use-case and why do you need this feature? I would like to create SDK for my API, that will be able to skip unknown types from new version. For example:

@Serializable
class Response(val data: List<SomeClass>)

@Serializable
sealed class SomeClass {
    @Serializable
    class A(val int: Int)

    @Serializable
    class B(val int: Int, val string: String)
}

When SDK will be used on client and API adds class C I would like client to ignore new class. Default behavior would crash client app.

For JSON I found solution like this:

open class IgnoreSerializationErrorListSerializer<E>(private val elementSerializer: KSerializer<E>) :
    KSerializer<List<E>> {
    private val listSerializer = ListSerializer(elementSerializer)
    override val descriptor: SerialDescriptor = listSerializer.descriptor

    override fun serialize(encoder: Encoder, value: List<E>) {
        listSerializer.serialize(encoder, value)
    }

    override fun deserialize(decoder: Decoder): List<E> = with(decoder as JsonDecoder) {
        decodeJsonElement().jsonArray.mapNotNull {
            try {
                json.decodeFromJsonElement(elementSerializer, it)
            } catch (e: SerializationException) {
                null
            }
        }
    }
}

object IgnoreUnknownResponse : IgnoreSerializationErrorListSerializer<SomeClass>(SomeClass.serializer())

Which I can then apply on response class and get expected result:

@Serializable
class Response(
    @Serializable(with = IgnoreUnknownResponse::class)
    val data: List<SomeClass>
)

Unfortunately it uses JsonDecoder so it won't work for Cbor ContentType

Describe the solution you'd like Best solution would be built in ListSerializer that would ignore errors on deserialization. It is very common good practice to be "precise with things you send and general with things you receive". Maybe making ArrayListSerializer open would help to, so I could override readElement method and skip builder.insert when decode fails:

override fun readElement(decoder: CompositeDecoder, index: Int, builder: Builder, checkIndex: Boolean) {
        builder.insert(index, decoder.decodeSerializableElement(descriptor, index, elementSerializer))
    }

I would be satisfied with some hack or workaround to make my own "IgnoreSerializationErrorListSerializer" work on Cbor / Protobuf decoders

sandwwraith commented 1 year ago

Isn't this duplicate of #1205 ?