How would I write a custom serializer for json inp...
# serialization
z
How would I write a custom serializer for json input that looks like this
[ { "somekey": { } }, { "someotherkey": { } } ]
I wanna be able to check the name of the first key in the object and then serialize to a specific class depending on what it is
l
If you have control over the input, you can create a sealed class and subclass it, marking each subclass with SerialName. This will create a ‘type’ field that will tell the serializer which subclass to deserialize. It won’t require a custom serializer.
Your json would look like
[ { "type": "MySubclass", "field1": "value1" }, { "type": "MyOtherClass", "anotherField": "value2" }, { "type": "ClassWithNoFields" } ]
z
unfortunately i dont have control over the input
e
Copy code
@Serializable(with = Foo.Serializer::class)
sealed class Foo {
    @Serializable
    object Bar : Foo()
    @Serializable
    object Baz : Foo()

    object Serializer : KSerializer<Foo> {
        override val descriptor: SerialDescriptor = SerialDescriptor("Foo", Surrogate.serializer().descriptor)
        override fun serialize(encoder: Encoder, value: Foo) {
            encoder.encodeSerializableValue(
                Surrogate.serializer(),
                Surrogate(
                    bar = value as? Bar,
                    baz = value as? Baz,
                )
            )
        }
        override fun deserialize(decoder: Decoder): Foo {
            val surrogate = decoder.decodeSerializableValue(Surrogate.serializer())
            return surrogate.bar ?: surrogate.baz ?: throw SerializationException("missing key")
        }
    }

    @Serializable
    private data class Surrogate(
        val bar: Bar? = null,
        val baz: Baz? = null,
    )
}

Json.decodeFromString<List<Foo>>("""[{"bar":{}},{"baz":{}}]""") == listOf(Foo.Bar, Foo.Baz)
r
Basically deserialize to JsonObject, check for your field in the JsonObject, then deserialize the JsonObject to whatever model matches
But please note that there is no such thing as a “first key” in a json object.
In json
{ "a": 0, "b": 1 }
and
{ "b": 1, "a": 0 }
are strictly identical
e
no need to go through JsonObject, the serializer I wrote above will work for all formats
r
I don’t like how your solution scales if you have 50 keys though 😄
e
yeah, that would be a pain. at that point you would either do some tricks to construct a delegate based on the PolymorphicSerializer directly, or use a JsonTransformingSerializer; parsing it directly from Json is not going to be better
e.g.
Copy code
object FooJsonSerializer : JsonTransformingSerializer<Foo>(Foo.serializer()) {
    private val discriminator = Foo::class.findAnnotation<JsonClassDiscriminator>()?.discriminator ?: "type"

    override fun transformSerialize(element: JsonElement): JsonElement = buildJsonObject {
        put(element.jsonObject[discriminator]!!.jsonPrimitive.content, JsonObject(element.jsonObject - discriminator))
    }

    override fun transformDeserialize(element: JsonElement): JsonElement = buildJsonObject {
        val (type, value) = element.jsonObject.entries.single()
        put(discriminator, type)
        for ((key, subvalue) in value.jsonObject) put(key, subvalue)
    }
}
(error-checking etc. omitted)