The @Serializable annotation enables the default serialization of all class properties with backing fields. You can customize this behavior to fit your specific needs. This page covers various serialization techniques, showing you how to specify which properties are serialized, and how the serialization process is managed.
The @Serializable annotation enables the automatic serialization of class properties, allowing a class to be converted to and from formats such as JSON.
In Kotlin, only properties with backing fields are serialized:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
class Project(
// Property with a backing field – serialized
var name: String
) {
// Property with a backing field – serialized
var stars: Int = 0
// Getter-only property without a backing field - not serialized
val path: String
get() = "kotlin/$name"
// Delegated property - not serialized
var id by ::name
}
fun main() {
val data = Project("kotlinx.serialization").apply { stars = 9000 }
// Prints only the name and the stars properties
println(Json.encodeToString(data))
// {"name":"kotlinx.serialization","stars":9000}
}
//sampleEnd
Serialize class references
Classes annotated with @Serializable can contain properties that reference other classes. The referenced classes must also be annotated with @Serializable. When encoded to JSON, this results in a nested JSON object:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// The owner property references another serializable class User
class Project(val name: String, val owner: User)
// The referenced class must also be annotated with @Serializable
@Serializable
class User(val name: String)
fun main() {
val owner = User("kotlin")
val data = Project("kotlinx.serialization", owner)
println(Json.encodeToString(data))
// {"name":"kotlinx.serialization","owner":{"name":"kotlin"}}
}
//sampleEnd
Serialization of repeated object references
Kotlin serialization is designed to encode and decode plain data. It doesn't support reconstruction of arbitrary object graphs with repeated object references. For example, when serializing an object that references the same instance twice, it's encoded twice:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
class Project(val name: String, val owner: User, val maintainer: User)
@Serializable
class User(val name: String)
fun main() {
val owner = User("kotlin")
// References owner twice
val data = Project("kotlinx.serialization", owner, owner)
println(Json.encodeToString(data))
// {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}}
}
//sampleEnd
Serialize generic classes
Generic classes in Kotlin support type-polymorphism, which is enforced by Kotlin serialization at compile-time. For example, consider a generic serializable class Payload<T>:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// The Payload<T> class can be used with built-in types like Int
// or with @Serializable user-defined types like Repository
class Payload<T>(val value: T)
@Serializable
data class Repository(val name: String, val language: String)
@Serializable
class BackupData(
val issueCount: Payload<Int>,
val mainRepo: Payload<Repository>
)
fun main() {
val backup = BackupData(
Payload(42),
Payload(Repository("kotlinx.serialization", "Kotlin"))
)
println(Json.encodeToString(backup))
// {"issueCount":{"value":42},"mainRepo":{"value":{"name":"kotlinx.serialization","language":"Kotlin"}}}
}
//sampleEnd
When you serialize a generic class like Box<T>, the JSON output depends on the actual type you specify for T at compile time. If that type isn't serializable, you get a compile-time error.
Optional properties
Properties with default values are optional during deserialization and can be skipped during serialization, if the format is configured to do so. For example, the default JSON configuration skips encoding default values.
Set default values for optional properties
In Kotlin, an object can be deserialized only when all its properties are present in the input. To make a property optional for serialization, set a default value that's used when no value is provided in the input:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// Sets a default value for the optional language property
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization"}
""")
println(data)
// Project(name=kotlinx.serialization, language=Kotlin)
}
//sampleEnd
Serialize nullable properties
Kotlin serialization natively supports nullable properties. Like other default values, null values aren't encoded in the JSON output:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// Defines a class with the renamedTo nullable property that has a null default value
class Project(val name: String, val renamedTo: String? = null)
fun main() {
val data = Project("kotlinx.serialization")
// The renamedTo property isn't encoded because its value is null
println(Json.encodeToString(data))
// {"name":"kotlinx.serialization"}
}
//sampleEnd
Additionally, Kotlin's null safety is strongly enforced during deserialization. If a JSON object contains a null value for a non-nullable property, an exception is thrown even when the property has a default value:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":null}
""")
println(data)
// JsonDecodingException
}
//sampleEnd
Initializers in optional properties
When the serialization input includes a value for an optional property, the property's initializer isn't called. For this reason, avoid using code with side effects in property initializers.
Here's an example:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
fun computeLanguage(): String {
println("Computing")
return "Kotlin"
}
@Serializable
// Skips the initializer if language is in the input
data class Project(val name: String, val language: String = computeLanguage())
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Java"}
""")
println(data)
// Project(name=kotlinx.serialization, language=Java)
}
//sampleEnd
In this example, since the language property is specified in the input, the Computing string isn't printed in the output.
Manage the serialization of default properties with @EncodeDefault
By default, JSON serialization excludes properties that have default values. This reduces the size of the serialized data and avoids unnecessary visual clutter.
Here's an example where the language property is excluded from the output because its value equals the default:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
val data = Project("kotlinx.serialization")
println(Json.encodeToString(data))
// {"name":"kotlinx.serialization"}
}
//sampleEnd
To always serialize a property, regardless of its value or format settings, use the @EncodeDefault annotation. Alternatively, you can set the EncodeDefault.Mode parameter to change this behavior.
Let's look at an example where the language property is always included in the serialized output, while the projects property is excluded when it's an empty list:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.EncodeDefault.Mode.NEVER
//sampleStart
@Serializable
data class Project(
val name: String,
// Always includes the language property in the serialized output
// even if it has the default value "Kotlin"
@EncodeDefault val language: String = "Kotlin"
)
@Serializable
data class User(
val name: String,
// Excludes projects when it's an empty list, even if it has a default value
@EncodeDefault(NEVER) val projects: List<Project> = emptyList()
)
fun main() {
val adminUser = User("Alice", listOf(Project("kotlinx.serialization")))
val guestUser = User("Bob")
// Serializes projects because it contains a value
// language is always serialized
println(Json.encodeToString(adminUser))
// {"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]}
// Excludes projects because it's an empty list
// and EncodeDefault.Mode is set to NEVER, so it's not serialized
println(Json.encodeToString(guestUser))
// {"name":"Bob"}
}
//sampleEnd
Make properties required with the @Required annotation
Annotate a property with @Required to make it required in the input. This enforces that the input contains the property, even if it has a default value:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.Required
//sampleStart
@Serializable
// Marks the language property as required
data class Project(val name: String, @Required val language: String = "Kotlin")
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization"}
""")
println(data)
// MissingFieldException
}
//sampleEnd
Customize class serialization
Kotlin serialization provides several ways to modify how classes are serialized. This section covers techniques for customizing property names, controlling property inclusion, and more.
Customize serial names
By default, property names in the serialization output, such as JSON, match their names in the source code.
You can customize these names, called serial names, with the @SerialName annotation. Use it to make a property name more descriptive in the serialized output:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// Changes the lang property to language using @SerialName
class Project(val name: String, @SerialName("language") val lang: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
// Prints the more descriptive property name in the JSON output
println(Json.encodeToString(data))
// {"name":"kotlinx.serialization","language":"Kotlin"}
}
//sampleEnd
Define constructor properties for serialization
A class annotated with @Serializable must declare all parameters in its primary constructor as properties.
If you need to perform additional initialization logic before assigning values to properties, use a secondary constructor. The primary constructor can remain private and handle property initialization.
Here's an example where the secondary constructor parses a string into two values, which are then passed to the primary constructor for serialization:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
class Project private constructor(val owner: String, val name: String) {
// Creates a Project object using a path string
constructor(path: String) : this(
owner = path.substringBefore('/'),
name = path.substringAfter('/')
)
val path: String
get() = "$owner/$name"
}
fun main() {
println(Json.encodeToString(Project("kotlin/kotlinx.serialization")))
// {"owner":"kotlin","name":"kotlinx.serialization"}
}
//sampleEnd
Validate data in the primary constructor
After deserialization, the kotlinx.serialization plugin runs the class's initializer blocks, just like when you create an instance. This allows you to validate constructor parameters and reject invalid data during deserialization.
Here's an example:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
//sampleStart
@Serializable
class Project(val name: String) {
// Validates that the name is not empty
init {
require(name.isNotEmpty()) { "name cannot be empty" }
}
}
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":""}
""")
println(data)
// Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty
}
//sampleEnd
Exclude properties with the @Transient annotation
You can exclude a property from serialization with the @Transient annotation. Transient properties must have a default value.
If you explicitly specify a value for a transient property in the input, even if it matches the default value, a JsonDecodingException is thrown:
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.Transient
import kotlinx.serialization.json.*
//sampleStart
@Serializable
// Excludes the language property from serialization
data class Project(val name: String, @Transient val language: String = "Kotlin")
fun main() {
// Throws an exception even though input matches the default value
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
println(data)
// JsonDecodingException
}
//sampleEnd