kotlinserializationkotlinx

can I use kotlinx serializer with multiple sealed class levels as parents and a nested invocation?


I am trying to use kotlinx @Serializable and Ive faced this issue:

I have the following classes:

@Serializable
sealed class GrandParent

a second one:

@Serializable
sealed class Parent() : GrandParent() {
       abstract val id: String
    }

and a third one

@Serializable
data class Child(
   override val id: String, ....
): Parent()

I'm needing of grandparent since I use it as a generic type in another class, which happen to also have a reference to the GrandParent class

@Serializable
data class MyContent(
   override val id: String,
   ....
   val data: GrandParent, <- so it has a self reference to hold nested levels
...): Parent()

Every time I try to run this I get an error...

Class 'MyContent' is not registered for polymorphic serialization in the scope of 'GrandParent'.
Mark the base class as 'sealed' or register the serializer explicitly.

I am using ktor as wrapper, kotlin 1.5.10. I did this based on https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#registered-subclasses

Any ideas?


Solution

  • You should serialize and deserialize using your sealed class in order for kotlin serialization to "know" to add a discriminator with the right implementation. By default it search for type in the json but you can change it with JsonBuilder:

    Json {
      classDiscriminator = "class"
    }
    

    Here is an example:

    @Serializable
    sealed class GrandParent
    
    @Serializable
    sealed class Parent : GrandParent() {
      abstract val id: String,
    }
    
    @Serializable
    data class Child(
      override val id: String,
    ): Parent()
    
    @Serializable
    data class MyContent(
      override val id: String,
      val data: GrandParent,
    ): Parent()
    
    fun main() {
      val test = MyContent(id = "test", data = Child(id = "child"))
    
      val jsonStr = Json.encodeToString(GrandParent.serializer(), test)
      println("Json string: $jsonStr")
    
      val decoded = Json.decodeFromString(GrandParent.serializer(), jsonStr)
      println("Decoded object: $decoded")
    }
    
    

    Result in console:

    Json string: {"type":"MyContent","id":"test","data":{"type":"Child","id":"child"}}
    Decoded object: MyContent(id=test, data=Child(id=child))
    

    encode and decode can also be written like this (but behind the scenes it will use reflections):

    val jsonStr = Json.encodeToString<GrandParent>(test)
    println("Json string: $jsonStr")
    
    val decoded = Json.decodeFromString<GrandParent>(jsonStr)
    println("Decoded object: $decoded")