10 minutes read

Serialization (encoding) is the process of converting data into a specific format, like XML, JSON, Protobuf, etc., so it can be transferred to different systems. The opposite process of converting the serialized data into the original format is called deserialization (decoding). There are many popular libraries for serializing and deserializing data in Java and Kotlin – Jackson, Gson, Fastjson, and Moshi. However, in this topic we will examine a pure-Kotlin library – the kotlinx.serialization library (Kotlin serialization). Kotlinx is a collection of useful libraries which do not belong to the standard library. The kotlinx.serialization library supports such serialization formats as JSON, HOCON, Protobuf, CBOR, and Properties. In this topic, we will deal with the popular JSON format.

Include the library in a project

In order to use the library with the JSON format, you need to edit the build.gradle file of your project. In the plugins, repositories, and dependencies sections, add the following lines:

plugins {
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
}

With the above lines, the Kotlin serialization plugin is loaded for Kotlin version 1.8.10, the Maven central repository is added, and the kotlinx JSON serialization dependency is included (version 1.3.2). The library is available in the Maven central repository, where you can find the latest version.

The library should be imported into the files of your project with the following lines:

import kotlinx.serialization.*
import kotlinx.serialization.json.*

Note that Kotlin compiler 1.4.0 or higher is required.

JSON encoding

With Kotlin serialization, any class that we want to serialize should be explicitly marked with the @Serializable annotation, which instructs the Kotlin serialization plugin to generate a serializer (code for encoding and decoding it) for it. The @Serializable annotation has to be put right before the class declaration.

Following is a simple example of serializing a class instance that has various types of properties. The Test class is annotated with the @Serializable annotation, while serialization is performed with the Json.encodeToString() function.

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
class Test(val name: String, val integerNumber: Int, val realNumber: Double, val listString: List<String>, val setInteger: Set<Int>)

fun main() {
    val listStr = listOf("One", "Two", "Three")
    val setNum = setOf(1, 0, 2, 9)
    val instance = Test("test", 42, 15.55,listStr, setNum)

    val serialized = Json.encodeToString(instance)
    println(serialized)
}

// {"name":"test","integerNumber":42,"realNumber":15.55,"listString":["One","Two","Three"],"setInteger":[1,0,2,9]}

Pretty printing

Normally, a JSON string is to be transmitted to another system, so it should be as short as possible. This is achieved by keeping it in a single line, without any unnecessary spaces. However, in some cases a JSON string should be human-readable, which can be accomplished by using the prettyPrint property of the Json object.

Let's change the following line of the previous code:

val serialized = Json.encodeToString(instance)

We will replace it with the following:

val jsonPrettyPrint = Json { prettyPrint = true }

val serialized = jsonPrettyPrint.encodeToString(instance)

Now the program output is:

{
    "name": "test",
    "integerNumber": 42,
    "realNumber": 15.55,
    "listString": [
        "One",
        "Two",
        "Three"
    ],
    "setInteger": [
        1,
        0,
        2,
        9
    ]
}

Encoding defaults

In many cases, a class constructor has properties with default values. If a class instance is initiated with the default value, then this property isn't serialized by default. Check out the following example:

@Serializable
class Player(val name: String, var leveL: Int = 1)

val jsonPrettyPrint = Json { prettyPrint = true }

val player1 = Player("Best player", 5)
println(jsonPrettyPrint.encodeToString(player1))
/* Prints:
{
    "name": "Best player",
    "leveL": 5
}
*/

val player2 = Player("John Doe")
println(jsonPrettyPrint.encodeToString(player2))
/* Prints:
{
    "name": "John Doe"
}
*/

If we want to change that behavior and encode the defaults, then the class property with the default value should be marked with the @EncodeDefault annotation as in the following example:

@Serializable
class Player(val name: String, @EncodeDefault var leveL: Int = 1)

val jsonPrettyPrint = Json { prettyPrint = true }

val player2 = Player("John Doe")
println(jsonPrettyPrint.encodeToString(player2))
/* Prints:
{
    "name": "John Doe",
    "leveL": 1
}
*/

JSON decoding

JSON deserialization is performed with the use of the Json.decodeFromString<SerializableClass>(jsonString: String) function, where SerializableClass is a serializable class and jsonString a JSON string. Following is an example of creating a Test class instance by decoding a JSON string:

@Serializable
data class Test(val name: String, val integerNumber: Int, val realNumber: Double, val listString: List<String>, val setInteger: Set<Int>)

val jsonString = """
    {
        "name": "myName",
        "integerNumber": 10,
        "realNumber": 12.0,
        "listString": [
            "First String",
            "Second String"
        ],
        "setInteger": [
            1000,
            2000
        ]
    }
""".trimIndent()

val testInstant = Json.decodeFromString<Test>(jsonString)

println(testInstant)

// Test(name=myName, integerNumber=10, realNumber=12.0, listString=[First String, Second String], setInteger=[1000, 2000])

In the above example, the Test class is marked as a data class. This is not a requirement for the deserialization process, but it is in order to easily print the Test instant properties.

Basic types and collections

Kotlin serialization directly supports such Kotlin basic types as Boolean, Byte, Short, Int, Long, Float, Double, Char, and String. In addition, the Enum, Pair, and Triple classes are also directly supported without the need to use the @Serializable annotation.

Kotlin List and Set types are serializable if their elements are serializable, while Kotlin Map is serializable if its keys and values are serializable. For example:

@Serializable
data class Person(val name: String)

val persons = listOf(Person("Joe Hill"), Person("Elen Doe"))
println(persons)
// [Person(name=Joe Hill), Person(name=Elen Doe)]

val jsonStr = Json.encodeToString(persons)
println(jsonStr)
// [{"name":"Joe Hill"},{"name":"Elen Doe"}]

val newPersons = Json.decodeFromString<List<Person>>(jsonStr)
println(newPersons)
// [Person(name=Joe Hill), Person(name=Elen Doe)]

Conclusion

Serialization plays a very important role in various programs, and kotlinx.serialization provides a fast and easy to use library for that purpose. In this topic, we've discussed only the basic features of the library; however, there is much more to it, which makes the library really helpful even in complex cases.

52 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo