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.
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.