Search code examples
jsonkotlinserializationpolymorphismsealed-class

KotlinX serialization - Polymorphic content using sealed


Let's assume I have following json objects :

{
    "type": "video",
    "...": "..."
}
{
    "type": "image",
    "...": "..."
}

They both represent media object. Kotlin sealed model looks like :

sealed class Media {
    ...
}

@Serializable
@SerialName("video")
data class Video(...) : Media()

@Serializable
@SerialName("image")
data class Image(...) : Media()

According KoltinX doc, I used a wrapper for polymorphic serialization :

@Serializable
private data class MediaWrapper(@Polymorphic val media: Media) {
    companion object {
        val jsonSerializer = Json(
            context = SerializersModule {
                polymorphic<Media> {
                    Video::class with Video.serializer()
                    Image::class with Image.serializer()
                }
            }
        )

        fun fromJson(json: String) = jsonSerializer.parse(serializer(), json)
    }
}

The goal is to deserialize a Media json using my wrapper, but problem is I need to change my Media json into a MediaWrapper json. The most convenient solution I found is to add {\"media\":\" & \"} on each side of my Media json:

sealed class Media {
    companion object {
        fun fromJson(mediaJson: String): Media {
            val mediaWrapperJson = "{\"media\":$mediaJson}"
            val mediaWrapper = MediaWrapper.fromJson(mediaWrapperJson)
            return mediaWrapper.media
        }
    }
}

This is a trick, if there is a more convenient way to deserialize polymorphics, please let me know!


Solution

  • While the kotlinx serialization docs use a wrapper in many of its polymorphic examples, it does not say that this pattern is mandatory.

    From the docs:

    Pro tip: to use Message without a wrapper, you can pass PolymorphicSerializer(Message::class) to parse/stringify.

    In your case you could do:

    sealed class Media {
        companion object {
            val jsonSerializer = Json(
                context = SerializersModule {
                    polymorphic<Media> {
                        Video::class with Video.serializer()
                        Image::class with Image.serializer()
                    }
                }
            )
    
            fun fromJson(mediaJson: String): Media {
               return jsonSerializer.parse(PolymorphicSerializer(Media::class), mediaJson) as Media
            }
        }
    }