Kotlin

Kotlin: Serialization – Update

Last week I published an article covering the kotlinx.serialisation library and how it had the potential to overcome the issue of Kotlin object serialization using Java Serialization not correctly handling the object singleton implementation which under-pins Kotlin objects. However, at the time I wrote that article, Kotlin sealed class hierarchies were not supported by kotlinx.serialisation, so we needed to take manual steps to serialize them. That is no longer the case and in this article we’ll look at how easy it has suddenly become to do this.

Blogging about technologies which are still under heavy development is often tricky because things can change before you’ve even published an article, and that is precisely what has happened in this instance. I wrote the original article back in early November and had a number of articles already lined up, so didn’t actually publish until early December. Between the two kotlinx.serialization version 0.14.0 was released (without me noticing) and this includes support for serializing Kotlin objects and sealed class hierarchies. I am extremely grateful to Hadi Hariri for alerting me to this, and highlighting an error (now fixed) in the previous post – thanks Hadi.

The first think that we need to do is update to version 0.14.0 of kotlinx.serialization and Kotlin 1.3.60 or later. At the time of writing we’re actually on 1.3.61 so let’s use that instead:

const val kotlinVersion = "1.3.61"

object BuildPlugins {
    const val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
    const val kotlinSerializationPlugin = "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"

    const val detekt = "io.gitlab.arturbosch.detekt"
    const val ktlint = "org.jlleitschuh.gradle.ktlint"
    const val versions = "com.github.ben-manes.versions"
    const val kotlinxSerialization = "kotlinx-serialization"
}

object Libraries {
    private object Versions {
        const val jupiter = "5.6.0-M1"
        const val kotlinxSerializationRuntime = "0.14.0"
    }

    const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
    const val kotlinxSerializationRuntime =
        "org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.kotlinxSerializationRuntime}"
    const val jupiter = "org.junit.jupiter:junit-jupiter:${Versions.jupiter}"

}

Our sealed class hierarchy can now be simplified to the version that I listed in the previous article (with a comment stating that it doesn’t work):

@Serializable
sealed class KSerializerSealedClass {

    @Serializable
    object Object : KSerializerSealedClass()

    @Serializable
    object Object2 : KSerializerSealedClass()

    @Serializable
    data class DataClass(val data: String) : KSerializerSealedClass()
}

We can now remove the ObjectSerializer.kt support code that we added in the previous article as it is no longer needed.

That’s all there is we need to do to our app code – it really is as simple as that. Having said that this is a pretty simple sealed class hierarchy, so we only need to add appropriate @Serializable annotations to both the sealed class itself and any concrete implementations. However there are some more complex use-cases which are covered in the updates to the official documentation, but the official docs are pretty good, and I can’t add any value to that so suggest you read it first-hand.

Although our app code now works, it’s not quite the end of the story because our unit tests no longer work. It’s not the tests themselves that are broken, but it is the function that we created to serialize then deserialize our objects in order to compare the before and after states:

    // This code no longer works
    private fun serialize(input: KSerializerSealedClass): KSerializerSealedClass {
        val serializer = Json(JsonConfiguration.Stable, KSerializerSealedClass.serializersModule)
        val json = serializer.stringify(KSerializerSealedClass.serializer, input)
        return serializer.parse(KSerializerSealedClass.serializer, json) as KSerializerSealedClass
    }

I’ve included this as a reminder of the previous implementation so that we can see that the changes we need to make actually simplify this, already pretty straightforward, function.

The first issue here is that KSerializerSealedClass.serializersModule no longer exists as it was part of the support code that we needed to add to KSerializerSealedClass in order to make the sealed class hierarchy serializable. But this is no longer needed.

The other change is to KSerializerSealedClass.serializer which also no longer exists, but the changes to kotlinx.serialization in 0.14.0 generate a function for us which does all of the heavy lifting, so we simply replace KSerializerSealedClass.serializer with KSerializerSealedClass.serializer() and we’re good to go.

The updated code looks like this:

    private fun serialize(input: KSerializerSealedClass): KSerializerSealedClass {
        val serializer = Json(JsonConfiguration.Stable)
        val json = serializer.stringify(KSerializerSealedClass.serializer(), input)
        return serializer.parse(KSerializerSealedClass.serializer(), json)
    }

Serializing and deserializing our sealed class hierarchy actually because even easier!

We can now run our unit test suite and it’s green across the board thanks to the support that was added to kotlinx.serialization in 0.14.0. The tests actually verify that the singleton behaviour intrinsic to Kotlin objects is survives serialisation and therefore verify that kotlinx.serialization does exactly what it should.

Although this particular issue has now been addressed, it is worth mentioning that kotlinx.serialization version 0.14.0 is still classified as experimental which means that it may not be feature complete, and future updates may introduce breaking API changes. I have only focussed on one specific use-case and there may well be other things that have yet to be implemented. That said, there is an awful lot to like, and this update shows that tangible progress is being made towards a release version.

The source code for this article is available here.

© 2019, Mark Allison. All rights reserved.

Copyright © 2019 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

2 Comments

  1. Hi Mark,
    I really liked your style. A very well explained tutorial. Following your website for android development

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.