Recently, I have been working on a project where I need to store sealed class data inside the Room Persistence using the Kotlinx Serialization library. We all know that Room only supports primitives types for storage. I struggle for almost two days just store the sealed classes using the Kotlinx Serialization library.

So, in this article, I’ll walk through you how to serialize and deserialize the sealed classes using the Kotlinx Serialization library.

To find out the basics of Kotlinx Serialization check out the following article.

Gradle Stuff

Kotlinx Serialization and Room Persistence require us to add some more stuff to our application module build.gradle file. We start by adding the following dependencies.

apply plugin: 'kotlin-kapt' // kapt plugin for code generation apply plugin: 'kotlinx-serialization' // kotlin serialization plugin dependencies { // Room architecture component dependencies implementation 'androidx.room:room-runtime:2.1.0' kapt 'androidx.room:room-compiler:2.1.0' implementation 'androidx.room:room-ktx:2.1.0' // Kotlinx Serialization implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0" }

We also need to add a classpath for Kotlinx Serialization library inside the top-level build.gradle file inside the buildScript->dependencies section.

buildscript { ext.kotlin_version = '1.3.40' repositories { google() jcenter() } dependencies { ...... classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } }

Structure of sealed class

The structure of my sealed class is pretty simple. I have one top-level sealed class and inside that class, there are several inheritance hierarchies. First, let’s see the structure.

sealed class AnsweredContent { @Serializable data class SingleTextContent(var answer: String = "") : AnsweredContent() @Serializable class MultiChoiceContent (val choices : List<String> = listOf<String>()) : AnsweredContent() { @Serializable data class LocationContent(var latitude: Double = 0.0, var longitude: Double = 0.0, var accuracy: Float = 0F) : AnsweredContent() }

Update: With the release of 0.13.0 version of kotlinx-serialization we just need to annotate our inner classes with @Serializable annotation.

Now let’s say I have this following class which holds the instance of my AnsweredContent sealed class.

@Serializable data class AnsweredContentHolder ( val id: Int, val title: String, @Polymorphic val answeredContent: AnsweredContent )

The @Polymorphic serialization annotation mostly used on sealed, abstract classes, and on interfaces as well.

Here is the definition of @Polymorphic annotation.

This annotation is applied to interfaces and serializable abstract classes and can be applied to open classes as well. It does not affect sealed classes because they are be serialized with sub-classes automatically.

Now to actually use this in action and convert the AnsweredContentHolder into JSON and back to object. We need to write a custom serializer for this.

Writing custom serializer

For the sake of simplicity, I have created a BaseSerializer<T> and define the generics toStringT , toTString method there. Have each custom serializer extends the BaseSerializer and add methods specific to each of them.

interface BaseSerializer<T> { val serializer: KSerializer<T> val format: StringFormat fun toStringT(json: String): T fun toTString(t: T): String }

And here is the implementation class of our generic serializer.

class AnsweredContentSerializer : BaseSerializer<AnsweredContentHolder> { private val sealedModule = SerializersModule { // 1 polymorphic(AnsweredContent::class) { AnsweredContent.SingleTextContent::class with AnsweredContent.SingleTextContent.serializer() AnsweredContent.MultiChoiceContent::class with AnsweredContent.MultiChoiceContent.serializer() AnsweredContent.LocationContent::class with AnsweredContent.LocationContent.serializer() } } override val serializer: KSerializer<AnsweredContentHolder> get() = AnsweredContentHolder.serializer() // 2 override val format: StringFormat get() = Json(context = sealedModule) // 3 override fun toStringT(json: String): AnsweredContentHolder { return format.parse(serializer, json) // 4 } override fun toTString(t: AnsweredContentHolder): String { return format.stringify(serializer, t) // 5 } }

Here is the step-by-step implementation of the above code.

If you remembered our AnsweredContent class has the @Polymorphic annotation on it. So, for the correct serialization and deserialization, we need to tell KSerializer how to do serialize and deserialize the AnsweredContent using the SerializersModule. Supply the serializer for AnsweredContentHolder because this class holds the reference of our AnsweredContent instance. Returning the instance of JSON by passing the sealedModule. Parse the json string into AnsweredContentHolder class instance. Convert the object into the string using the StringFormat.

Testing kotlinx serialization and deserialization with Object

In order to test the serialization and deserialization, we just need to get the instance of AnsweredContentSerializer and pass the valid parameter to its function. Let’s see the example.

fun main() { val answeredContentHolder = AnsweredContentHolder(1, "Ahsen Saeed", AnsweredContent.LocationContent(34.515451, 74.568651, 22F)) val serializer = AnsweredContentSerializer() val json = serializer.toTString(answeredContentHolder) val newAnsweredContent = serializer.toStringT(json) println("Json content -> $json") println("Answered Content -> $newAnsweredContent") } // The output of above program Json content -> {"id":1,"title":"Ahsen Saeed","answeredContent":{"type":"AnsweredContent.LocationContent","latitude":34.515451,"longitude":74.568651,"accuracy":22.0}} Answered Content -> AnsweredContentHolder(id=1, title=Ahsen Saeed, answeredContent=LocationContent(latitude=34.515451, longitude=74.568651, accuracy=22.0))

Testing kotlinx serialization and deserialization with Collection

Now let’s say if I need to serialize and deserialize the list of AnsweredContentHolder using our generic serializer. So, for that, we need to update our AnsweredContentSerializer and pass the List<AnsweredContentHolder> instead of simple class for generic.

class AnsweredContentSerializer : BaseSerializer<List<AnsweredContentHolder>> { private val sealedModule = SerializersModule { polymorphic(AnsweredContent::class) { AnsweredContent.SingleTextContent::class with AnsweredContent.SingleTextContent.serializer() AnsweredContent.MultiChoiceContent::class with AnsweredContent.MultiChoiceContent.serializer() AnsweredContent.LocationContent::class with AnsweredContent.LocationContent.serializer() } } override val serializer: KSerializer<List<AnsweredContentHolder>> get() = AnsweredContentHolder.serializer().list override val format: StringFormat get() = Json(context = sealedModule) override fun toStringT(json: String): List<AnsweredContentHolder> { return format.parse(serializer, json) } override fun toTString(t: List<AnsweredContentHolder>): String { return format.stringify(serializer, t) } }

The only noticeable thing in the above code is, you see we’re returning KSerializer<List<AnsweredContentHolder>> instead of simple KSerializer.

And here’s how to convert the collection into string and back to collection.

fun main() { val answeredContentHolders = listOf(AnsweredContentHolder(1, "Ahsen Saeed", AnsweredContent.LocationContent(34.515451, 74.568651, 22F)), AnsweredContentHolder(2, "Coding Infinite", AnsweredContent.SingleTextContent("Hello World"))) val serializer = AnsweredContentSerializer() val json = serializer.toTString(answeredContentHolders) val newAnsweredContents = serializer.toStringT(json) println("Json content -> $json") println("Answered Content -> $newAnsweredContents") } // The output of above program Json content -> [{"id":1,"title":"Ahsen Saeed","answeredContent":{"type":"AnsweredContent.LocationContent","latitude":34.515451,"longitude":74.568651,"accuracy":22.0}},{"id":2,"title":"Coding Infinite","answeredContent":{"type":"AnsweredContent.SingleTextContent","answer":"Hello World"}}] Answered Content -> [AnsweredContentHolder(id=1, title=Ahsen Saeed, answeredContent=LocationContent(latitude=34.515451, longitude=74.568651, accuracy=22.0)), AnsweredContentHolder(id=2, title=Coding Infinite, answeredContent=SingleTextContent(answer=Hello World))]

Alright, guys, this was my understanding of how to serialize and deserialize the sealed classes through Kotlinx Serialization library. If you any queries or suggestion on improving the above example please let me know in the comments section.

Thank you for being here and keep reading…