https://kotlinlang.org logo
Title
f

F0X

04/05/2023, 10:55 PM
I'm trying to implement a custom polymorphic-like Serializer (the
id
field of an object determines what the other fields are), and I'm having trouble understanding the existing
SealedClassSerializer
/`AbstractPolymorphicSerializer`. When using such a serializer, the fields of the class are directly within the same object as the
type
field, as shown in this example:
@Serializable
sealed class Project {
    abstract val name: String
}
            
@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(Json.encodeToString(data)) // Serializing data of compile-time type Project

    // prints: {"type":"example.examplePoly04.OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"}
    // NOT: {"type":"example.examplePoly04.OwnedProject", "value": {"name":"kotlinx.coroutines","owner":"kotlin"}}
}
But I do not see how this is achieved in the code, where
serialize
is implemented like this:
public final override fun serialize(encoder: Encoder, value: T) {
        val actualSerializer = findPolymorphicSerializer(encoder, value)
        encoder.encodeStructure(descriptor) {
            encodeStringElement(descriptor, 0, actualSerializer.descriptor.serialName)
            encodeSerializableElement(descriptor, 1, actualSerializer.cast(), value)
        }
    }
To me it looks like this should result in two fields like the alternative I added in the example above, but somehow with this weird descriptor it works:
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
        buildSerialDescriptor(serialName, PolymorphicKind.SEALED) {
            element("type", String.serializer().descriptor)
            val elementDescriptor =
                buildSerialDescriptor("kotlinx.serialization.Sealed<${baseClass.simpleName}>", SerialKind.CONTEXTUAL) {
                    // serialName2Serializer is guaranteed to have no duplicates — checked in `init`.
                    serialName2Serializer.forEach { (name, serializer) ->
                        element(name, serializer.descriptor)
                    }
                }
            element("value", elementDescriptor)
            annotations = _annotations
        }
    }
Why is that and how can I do the same for my own custom serializer, which requires basically the same logic, but isn't based on a class hierarchy?
b

Ben Woodworth

04/07/2023, 7:10 PM
I believe Json handles the Sealed/AbstractPolymorphic serializers specially, which is why it doesn't serialize the way you'd expect from looking at the serializer Have you looked into the JsonContentPolymorphicSerializer? Using that might be what you want. Or at least, you might be able to learn something from its implementation
f

F0X

04/21/2023, 8:33 AM
Thank you for your answer. In the end I wasn't able to successfully make my own version of polymorphic serialization (as you said there seems to be a lot of special stuff going on, and I didn't manage to adapt the JsonContentPolymorphicSerializer to my specific usecase). Luckily I could refactor the system to use standard Kotlin polymorphism, so I don't need any of this anymore and am now using normal open polymorphism registering the classes in the serializers module. A few tradeoffs were necessary, so I still think it would be nice if one could replicate the polymorphic serialization behaviour in their own serializers without the special handling from kotlinx.serialization.