Kotlin Serialization: A Deep Dive

In this tutorial, we will take a deep dive into Kotlin Serialization, exploring its basic concepts, serializing and deserializing objects, and exploring advanced features such as polymorphic serialization and integration with other libraries. We will also provide code examples and step-by-step explanations for each section to help you understand and implement Kotlin Serialization in your own projects.

kotlin serialization deep dive

Introduction

What is Kotlin Serialization?

Kotlin Serialization is a framework that allows you to convert Kotlin objects into a format that can be easily stored or transmitted, such as JSON or XML. It provides a simple and efficient way to serialize and deserialize objects, making it easier to work with data in your Kotlin applications.

Why is Kotlin Serialization important?

Serialization is an essential part of many applications, especially those that involve data storage, network communication, or inter-process communication. Kotlin Serialization simplifies the process of converting objects into a serializable format, reducing the amount of code you need to write and improving the performance of your application.

How does Kotlin Serialization work?

Kotlin Serialization works by using annotations to define how objects should be serialized and deserialized. By annotating your classes and properties with specific serialization annotations, you can control how the objects are converted into a serializable format and how they are reconstructed when deserialized.

Basic Concepts

Annotations

Annotations play a crucial role in Kotlin Serialization. They allow you to define how objects should be serialized and deserialized. The two main annotations used in Kotlin Serialization are @Serializable and @SerialName.

The @Serializable annotation is used to mark a class as serializable. By annotating a class with @Serializable, you indicate that its instances can be converted into a serializable format.

@Serializable
data class Person(val name: String, val age: Int)

The @SerialName annotation is used to specify the name of a property when serialized or deserialized. This is useful when you want to use a different name for a property in the serialized form.

@Serializable
data class Person(@SerialName("person_name") val name: String)

Serializers

Serializers are responsible for converting objects into a serializable format. Kotlin Serialization provides a set of built-in serializers for common data types such as strings, numbers, and booleans. You can also create custom serializers for more complex data types.

@Serializable
data class Person(val name: String, val age: Int)

val person = Person("John", 30)

val jsonString = Json.encodeToString(Person.serializer(), person)

In the example above, we create a Person object and use the Json.encodeToString function to convert it into a JSON string. We specify the serializer for the Person class using Person.serializer().

Deserializers

Deserializers, as the name suggests, are responsible for converting a serializable format back into an object. Kotlin Serialization provides a set of built-in deserializers that can convert JSON strings, XML, and other formats back into Kotlin objects.

@Serializable
data class Person(val name: String, val age: Int)

val jsonString = "{\"name\":\"John\",\"age\":30}"

val person = Json.decodeFromString<Person>(jsonString)

In the example above, we have a JSON string representing a Person object. We use the Json.decodeFromString function to convert the JSON string back into a Person object.

Serializing Objects

Serializing simple data types

Serializing simple data types such as strings, numbers, and booleans is straightforward with Kotlin Serialization. The framework provides built-in serializers for these types, allowing you to convert them into a serializable format with ease.

@Serializable
data class Person(val name: String, val age: Int)

val person = Person("John", 30)

val jsonString = Json.encodeToString(Person.serializer(), person)

In the example above, we create a Person object with a name and age. We then use the Json.encodeToString function to convert the Person object into a JSON string.

Serializing custom objects

Kotlin Serialization also supports serializing custom objects. By default, Kotlin Serialization will automatically serialize all properties of a class, as long as they are serializable themselves. However, you can customize the serialization process by using annotations and custom serializers.

@Serializable
data class Address(val street: String, val city: String)

@Serializable
data class Person(val name: String, val age: Int, val address: Address)

val person = Person("John", 30, Address("Main Street", "New York"))

val jsonString = Json.encodeToString(Person.serializer(), person)

In the example above, we define a Person class with an additional Address property. We create a Person object and use the Json.encodeToString function to serialize it into a JSON string.

Handling nullable properties

Kotlin Serialization also supports serializing and deserializing nullable properties. By default, nullable properties are serialized as null if their value is null. When deserializing, nullable properties are set to null if the corresponding value is missing or null.

@Serializable
data class Person(val name: String, val age: Int, val address: Address?)

val jsonString = "{\"name\":\"John\",\"age\":30}"

val person = Json.decodeFromString<Person>(jsonString)

In the example above, we define a Person class with an optional Address property. We have a JSON string that does not include the address property. When deserializing, the address property will be set to null.

Deserializing Objects

Deserializing simple data types

Deserializing simple data types such as strings, numbers, and booleans is straightforward with Kotlin Serialization. The framework provides built-in deserializers for these types, allowing you to convert them back into Kotlin objects with ease.

@Serializable
data class Person(val name: String, val age: Int)

val jsonString = "{\"name\":\"John\",\"age\":30}"

val person = Json.decodeFromString<Person>(jsonString)

In the example above, we have a JSON string representing a Person object. We use the Json.decodeFromString function to convert the JSON string back into a Person object.

Deserializing custom objects

Kotlin Serialization also supports deserializing custom objects. By default, Kotlin Serialization will automatically deserialize all properties of a class, as long as they are deserializable themselves. However, you can customize the deserialization process by using annotations and custom serializers.

@Serializable
data class Address(val street: String, val city: String)

@Serializable
data class Person(val name: String, val age: Int, val address: Address)

val jsonString = "{\"name\":\"John\",\"age\":30,\"address\":{\"street\":\"Main Street\",\"city\":\"New York\"}}"

val person = Json.decodeFromString<Person>(jsonString)

In the example above, we define a Person class with an additional Address property. We have a JSON string that includes the address property. When deserializing, the address property will be deserialized into an Address object.

Handling nullable properties

Kotlin Serialization also supports deserializing nullable properties. By default, nullable properties are set to null if the corresponding value is missing or null in the serializable format.

@Serializable
data class Person(val name: String, val age: Int, val address: Address?)

val jsonString = "{\"name\":\"John\",\"age\":30}"

val person = Json.decodeFromString<Person>(jsonString)

In the example above, we define a Person class with an optional Address property. We have a JSON string that does not include the address property. When deserializing, the address property will be set to null.

Advanced Features

Polymorphic serialization

Polymorphic serialization allows you to serialize and deserialize objects with a common superclass or interface. Kotlin Serialization provides support for polymorphic serialization through the use of @Polymorphic and @PolymorphicSerializer annotations.

@Serializable
@Polymorphic
sealed class Shape

@Serializable
@SerialName("circle")
data class Circle(val radius: Double) : Shape()

@Serializable
@SerialName("rectangle")
data class Rectangle(val width: Double, val height: Double) : Shape()

val shape: Shape = Circle(5.0)

val jsonString = Json.encodeToString(Shape.serializer(), shape)

In the example above, we define a Shape sealed class with two subclasses, Circle and Rectangle. We have a shape object of type Shape that is actually an instance of Circle. We use the Json.encodeToString function to serialize the shape object into a JSON string.

Custom serializers

Kotlin Serialization allows you to create custom serializers for more complex data types or to customize the serialization process for specific properties. Custom serializers can be defined by implementing the KSerializer interface.

@Serializable
data class Person(val name: String)

object PersonSerializer : KSerializer<Person> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Person", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Person) {
        encoder.encodeString(value.name)
    }

    override fun deserialize(decoder: Decoder): Person {
        val name = decoder.decodeString()
        return Person(name)
    }
}

val person = Person("John")

val jsonString = Json.encodeToString(PersonSerializer, person)

In the example above, we define a custom serializer for the Person class. The custom serializer implements the KSerializer interface and provides the necessary functions for serialization and deserialization. We use the custom serializer with the Json.encodeToString function to serialize the person object into a JSON string.

Serializing sealed classes

Kotlin Serialization supports serializing and deserializing sealed classes, which are classes that have a fixed set of subclasses. Sealed classes are useful for modeling hierarchies where the number of subclasses is known and limited.

@Serializable
sealed class Result

@Serializable
data class Success(val message: String) : Result()

@Serializable
data class Error(val code: Int, val message: String) : Result()

val result: Result = Success("Operation succeeded")

val jsonString = Json.encodeToString(Result.serializer(), result)

In the example above, we define a Result sealed class with two subclasses, Success and Error. We have a result object of type Result that is actually an instance of Success. We use the Json.encodeToString function to serialize the result object into a JSON string.

Integration with other libraries

Kotlinx.serialization with Retrofit

Kotlin Serialization can be seamlessly integrated with Retrofit, a popular HTTP client for Android and Java. By using the kotlinx-serialization converter, you can easily serialize and deserialize Kotlin objects in your Retrofit API interfaces.

@Serializable
data class Person(val name: String, val age: Int)

interface ApiService {
    @GET("person/{id}")
    suspend fun getPerson(@Path("id") id: Int): Person
}

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(Json.asConverterFactory(MediaType.get("application/json")))
    .build()

val apiService = retrofit.create(ApiService::class.java)

val person = apiService.getPerson(1)

In the example above, we define an ApiService interface that includes a method for retrieving a Person object from the API. We use the Json.asConverterFactory function to create a converter factory that supports Kotlin Serialization. When making API calls, Retrofit will automatically serialize and deserialize the Person objects using Kotlin Serialization.

Kotlinx.serialization with Room

Kotlin Serialization can also be integrated with Room, a popular database library for Android. By using Kotlin Serialization, you can store and retrieve Kotlin objects in your Room database without the need for manual conversion.

@Serializable
data class Person(val name: String, val age: Int)

@Dao
interface PersonDao {
    @Query("SELECT * FROM person")
    suspend fun getAllPersons(): List<Person>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertPerson(person: Person)
}

@Database(entities = [Person::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun personDao(): PersonDao
}

val appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, "app_database")
    .build()

val personDao = appDatabase.personDao()

val persons = personDao.getAllPersons()

In the example above, we define a Person data class and a PersonDao interface with methods for retrieving and inserting Person objects. We also define an AppDatabase class that extends RoomDatabase. The Person objects are automatically serialized and deserialized using Kotlin Serialization when stored and retrieved from the Room database.

Kotlinx.serialization with Ktor

Kotlin Serialization can be integrated with Ktor, a lightweight web framework for Kotlin. By using the kotlinx-serialization plugin for Ktor, you can easily serialize and deserialize Kotlin objects in your HTTP routes.

@Serializable
data class Person(val name: String, val age: Int)

val server = embeddedServer(Netty, port = 8080) {
    install(ContentNegotiation) {
        json(Json {
            serializer = KotlinxSerializer(Json)
        })
    }

    routing {
        get("/person/{id}") {
            val id = call.parameters["id"]?.toIntOrNull()
            val person = Person("John", 30)
            call.respond(person)
        }
    }
}

server.start(wait = true)

In the example above, we define a Ktor server that includes a route for retrieving a Person object. We use the KotlinxSerializer provided by the kotlinx-serialization plugin to serialize the Person object. When the request is made, Ktor will automatically serialize the Person object using Kotlin Serialization and include it in the response.

Conclusion

In this tutorial, we have explored Kotlin Serialization in depth, covering its basic concepts, serializing and deserializing objects, and exploring advanced features such as polymorphic serialization and integration with other libraries. We have provided code examples and step-by-step explanations to help you understand and implement Kotlin Serialization in your own projects. With Kotlin Serialization, you can simplify the process of converting objects into a serializable format, making it easier to work with data in your Kotlin applications.