Skip to content

Getting Started with Kotlinx Serialization

A couple weeks ago, I benchmarked Gson, Jackson, and Moshi against each other. TLDR: Jackson is a speedy boi. If you’re interested, read the full article below:

Benchmarking Gson vs Jackson vs Moshi 2020

However, what I didn’t test was the Jackson Kotlin module. And from the suggestions of my fellow Reddit guys in r/androiddev, why not benchmark Kotlinx Serialization too?

I had only come across this for the first time in my initial research about the JSON Parsers about a month ago. Of course, I needed to learn it first before benchmarking it against Jackson-Kotlin, so hey here’s another Getting Started post.

Initial Thoughts

Kotlinx Serialization isn’t a name I’ve heard until quite recently. In fact, the first stable version of the library 1.0 was released just last month!

And it is now viable for use in production code!…. or so it says. While that may be the case in pure Kotlin or Kotlin Multiplatform projects, I wouldn’t be so sure for Android.

Importing the library into Gradle was a nightmare on my new Android project running Gradle 6.7, also thanks in part to duplicate class errors with Jake Wharton’s Retrofit Kotlinx Serialization Converter which I’m also using because you know, Retrofit.

So the only solution that worked for me was to use the still beta version 0.20.0 of the Kotlinx Serialization library. I’ll show you my Gradle configuration below, but if you’re reading this and are already more familiar with Kotlinx Serialization than me and can suggest the correct way to make it work with Jake Wharton’s library, please stick it down on the comments.

Set Up

In your project-level build.gradle file, add the classpath dependency.

classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"

Then on top of your app-level build.gradle file, add the plugin.

plugins {
    ...
    id "kotlinx-serialization"
}

Then add the depedency of the library as well as Jake Wharton’s converter library.

implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.5.0"

These versions are what worked specifically for me without any gradle errors.

Building the Retrofit Interface

private val contentType =  "application/json".toMediaType()
private val jsonConfig = JsonConfiguration.Stable.copy(prettyPrint = true, ignoreUnknownKeys = true)
private val json = Json(jsonConfig)

private val userApi = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(json.asConverterFactory(contentType))
    .build()
    .create(UserApi::class.java)

The converter factory you’re adding to Retrofit requires a Json object which is where we set up our configuration. We added the ignoreUnknownKeys property because we’re parsing an object where we haven’t defined the full set of properties in our data class as it is in the JSON we’re consuming.

The rest is just your standard Retrofit biz.

If you’re getting an unresolved reference on toMediaType, just know that’s from the okHttp library which you need to import into Gradle.

Data Object

@Serializable
data class User(
    val id: Int,
    val name: String,
    val email: String
)

An API-consuming data object for Kotlinx is defined with the @Serializable annotation.

A useful property of Kotlinx is if you have nested data classes and haven’t defined any of them with @Serializable, the IDE will give you an error.

Another annotation you can use on fields is @Transient, which tells Kotlinx to completely ignore that field whilst serializing and deserializing.

All fields are considered optional, so if you have a field that’s in the data class but not in the API, it simply returns null… OR if you set a default value on it, it will remain that default value.This is one of the defining traits of Kotlinx Serialization. With most of the other json parsers, even if you set a default value like with the above code, if name where to not be found in the API, they would be null. In Kotlinx, it defaults back to the default value.

Finally, if you have a variable name that’s different in the API as how you want it in the class (i.e. if name was username in the API), then you can use the @SerialName annotation to define that.

@SerialName("username")
val name: String

And with that, you can make API calls with the default Kotlinx deserializer.

Custom Serializers & Deserializers

Of course, you can make custom serializers and deserializers with Kotlinx, I’ve given it a go but I found it more complex than those of Gson, Jackson, and Moshi.

For that reason alone, I left it out of this post, but let me know if you do want me to cover it and I’ll do a proper look into it.

Why use Kotlinx Serialization

Built for the Kotlin type adapter

Kotlinx is built completely with Kotlin which also makes it perfect for use with Kotlin Multiplatform, Kotlin/Native, and Kotlin/JS. But what about in the context of Android?

This means it doesn’t have to work via reflection as other json parsers do, which is considered a bad practice in Android, and won’t work at all in Kotlin/JS and native modules.

But rather, the bigger part of having a Kotlin-based library code is when we get to generics. Imagine you have a type such as this to serialize.

val wordsList: List<String>

Other non-Kotlin based json parser libraries would suffer from type erasure, meaning knowledge of their generic type is lost. The way they usually get around this is passing in a type token as a parameter in their serialization / deserialization functions which Kotlinx simply doesn’t have to do.

Other advantages of Kotlinx include:

  • Polymorphic Serialization
  • Strong Customizability
  • Framework Integration
  • Multi-Format Future

These are according to Jetbrain’s blog post on its official release.

Conclusion

So far, I can’t say I’m too impressed with Kotlinx Serialization. I can see how great it is for other Kotlin platforms, but in Android, I don’t see why I’d be using it over Jackson or Moshi. Even Gson if I’m being honest.

To me, it feels more tedious than it needs to be without being as feature rich as the other parsers. Sure it’s great that it’s got that Kotlin-based library to get around type erasure and all that, but that’s only a very minor setback for the other parsers.

I can’t say how reliable it is, how well it handles errors, however, we will see how well and how fast it performs in a new benchmark test along with the Jackson Kotlin module.

Stay tuned for that next week. Until then, happy coding ༼ つ ◕_◕ ༽つ