'Polymorphic deserialization of nested list object in Kotlin

Trying to deserialize a pretty complex json string, I've done simpler ones but am struggling with how to set this up. I've also found some examples on simple polymorphic deserialization but cannot figure out how to adapt it for this more complex situation where I have different nested objects some of which are polymorphic and some of which aren't. Any help would be really appreciated!

@Serializalbe 
data class Object1(
    @SerialName("id")
    val id: String,
    @SerialName("object2"),
    val object2: List<Object2>
)

data class Object2(
    @SerialName("name")
    val name: String,
    @SerialName("object2"),
    val object3: List<Object3>
)

@Polymorphic
interface Object3: Parcelable {

    val id: String

    /**
     * [Object3.Item1],
     * [Object3.Item2]
     */
    val type: Object3.Type

    val data: Data

    val options: List<Options>
}


@Serializable
@SerialName(type)
@Parcelize
data class Item1(
    @SerialName("_version")
    val version: String,
    @SerialName("info")
    override val info: AnotherPolyMorphicItem,
    
) : Object3 {
    override val type: Type get() = Object3.Type.Item1
}

@Serializable
@SerialName(type)
@Parcelize
data class Item2(
    @SerialName("_title")
    val tile: String,
    @SerialName("info")
    val info: AnotherPolymorphicItem,
    @SerialName("post")
    val post: String
    
) : Object3 {
    override val type: Type get() = Object3.Type.Item2
}

I have built this for the Object3 deserialization:

object Object3Json : SerializerContainer {
    override val serializer =
        Json {
            isLenient = false
            ignoreUnknownKeys = false
            allowSpecialFloatingPointValues = true
            useArrayPolymorphism = false
            ignoreUnknownKeys = true
            useAlternativeNames = false
            classDiscriminator = "type"
            serializersModule = SerializersModule {
                polymorphic(Object3::class) {
                    subclass(Item1::class, Item1.serializer())
                }
                polymorphic(Object3::class) {
                    subclass(Item2::class, Item2.serializer())
                }
                
            }
        }
}

I have also been deserializing simpler json strings as follows:

object DefaultJson : SerializerContainer {
    override val serializer: Json by lazy {
        Json {
            isLenient = false
            useAlternativeNames = false
        }
    }
}

val responseObject = DefaultJson.serializer.decodeFromString(
                        ResponseObject.serializer(), payload
                    )

@Serializable
data class ResponseObject(
    @SerialName("data1")
    val data1: String,
    @SerialName("data2")
    val data2: String
    )

My issue is how to combine the more simple deserialization with the polymorphism and also how to deserialize into lists of objects, like if I wanted to deserialize a list of Object1's I've followed the examples I can find that throws errors.



Solution 1:[1]

So this is a lot simpler than I thought. What it returns looks a little funny until you get into the polymorphic class but all the data is accessible using the data class structures I set up.

I created dataClass to hold a list of Object1's and then slightly tweaked the response to include that so I now had:

@Serializable
data class Object1List(
   @SerialName("object1")
   val object1: List<Object1>
)

Then I deserialized it as follows, with payload being the JSON string I was deserializing.

val response = Object3Json.serializer.decodeFromString(Object1List.serializer(), payload)

Solution 2:[2]

Our project has some polymorphic serialization, but I don't know if you'd consider it complex. We've achieved all the serialisation with standard Jackson with careful hinting with the @JsonTypeInfo/@JsonSubTypes annotations.

Here is a code fragment - by just having a type indicator in the class we have have achieved round-trip serialization without any custom serializers. In this case we have 6 types of Event which use one of 3 concrete classes (some details like interfaces, all the class fields ommitted for brevity):

enum class EventType(val eventClass: KClass<out Event>) {
    CLINICIAN_MEMBER_CHANNEL_MESSAGE_TO_CLINICIAN(MemberClinicianMessageEvent::class),
    CLINICIAN_MEMBER_CHANNEL_MESSAGE_TO_MEMBER(MemberClinicianMessageEvent::class),
    MENTOR_MEMBER_CHANNEL_MESSAGE_TO_MENTOR(MentorMemberMessageEvent::class),
    MENTOR_MEMBER_CHANNEL_MESSAGE_TO_MEMBER(MentorMemberMessageEvent::class),
    CARD_ACTION_TO_MEMBER(CardActionEvent::class),
    CARD_ACTION_TO_CLINICIAN(CardActionEvent::class),
}

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "eventType",
    visible = true
)
@JsonSubTypes(
    JsonSubTypes.Type(name = "CLINICIAN_MEMBER_CHANNEL_MESSAGE_TO_CLINICIAN", value = MemberClinicianMessageEvent::class),
    JsonSubTypes.Type(name = "CLINICIAN_MEMBER_CHANNEL_MESSAGE_TO_MEMBER", value = MemberClinicianMessageEvent::class),
    JsonSubTypes.Type(name = "MENTOR_MEMBER_CHANNEL_MESSAGE_TO_MENTOR", value = MentorMemberMessageEvent::class),
    JsonSubTypes.Type(name = "MENTOR_MEMBER_CHANNEL_MESSAGE_TO_MEMBER", value = MentorMemberMessageEvent::class),
    JsonSubTypes.Type(name = "CARD_ACTION_TO_MEMBER", value = CardActionEvent::class),
    JsonSubTypes.Type(name = "CARD_ACTION_TO_CLINICIAN", value = CardActionEvent::class),
)


data class MemberClinicianMessageEvent(
    override val id: RULID,
    override val eventType: EventType,
    override val createdBy: SkinnyProfile,
...skip...
) : StackEvent

data class MentorMemberMessageEvent(
    override val id: RULID,
    override val eventType: EventType,
    override val createdBy: SkinnyProfile,
...skip...
) : StackEvent

data class CardActionEvent(
    override val id: RULID,
    override val eventType: EventType,
    override val createdBy: SkinnyProfile,
...skip...
) : StackEvent

eventClass in the first enum is not needed for serialization, but allows us elsewhere to perform type validation.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeremy Caney
Solution 2 AndrewL