From 8f0d35ac689e7204b27d1ce1d9104444b7771f68 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 23 Oct 2025 17:29:02 +0200 Subject: [PATCH 1/5] feat: adding the Serialize classes page --- .../serialization-customization-options.md | 505 ++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 docs-website/topics/serialization-customization-options.md diff --git a/docs-website/topics/serialization-customization-options.md b/docs-website/topics/serialization-customization-options.md new file mode 100644 index 000000000..7a33aa69f --- /dev/null +++ b/docs-website/topics/serialization-customization-options.md @@ -0,0 +1,505 @@ +[//]: # (title: Serialize classes) + +The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. +You can further 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. + +Before starting, make sure you have the [necessary library dependencies imported](serialization-get-started.md). + +## The @Serializable annotation + +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. +Properties defined with [accessors](properties.md) or delegated properties without backing fields are excluded from serialization: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +class Project( + // Declares a property with a backing field - serialized + var name: String +) { + // Declares a property with a backing field - serialized + var stars: Int = 0 + + // Defines a getter-only property without a backing field - not serialized + val path: String + get() = "kotlin/$name" + + // Declares a 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 +``` +{kotlin-runnable="true"} + +Kotlin Serialization natively supports nullable properties. +Like [other _default values_](#set-default-values-for-optional-properties), `null` values aren't encoded in the JSON output: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +// Declares 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 +``` +{kotlin-runnable="true"} + +Additionally, Kotlin's [null safety](null-safety.md) 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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization","language":null} + """) + println(data) + // JsonDecodingException +} +//sampleEnd +``` +{kotlin-runnable="true" validate="false"} + +> If you need to handle `null` values from third-party JSON, you can [coerce them to a default value](serialization-json-configuration.md#coerce-input-values). +> +{style="tip"} + +Similarly, when the input contains an [optional property](#set-default-values-for-optional-properties) (a property with a default value), its initializer isn't called. +For this reason, avoid using code with side effects in property initializers. + +Here's an example: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization","language":"Java"} + """) + println(data) + // Project(name=kotlinx.serialization, language=Java) +} +//sampleEnd +``` +{kotlin-runnable="true"} + +In this example, since the `language` property is specified in the input, the `Computing` string isn't printed +in the output. + +### Serialization of class references + +Serializable classes 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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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 +``` +{kotlin-runnable="true"} + +> To reference non-serializable classes, annotate the corresponding properties with [`@Transient`](#exclude-properties-with-the-transient-annotation), or +> provide a [custom serializer](create-custom-serializers.md) for them. +> +{style="tip"} + +### 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 simply encoded twice: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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 +``` +{kotlin-runnable="true"} + +> If you attempt to serialize a circular structure, it results in stack overflow. +> To exclude references from serialization, use [the @Transient annotation](#exclude-properties-with-the-transient-annotation). +> +{style="tip"} + +### Generic class serialization + +Generic classes in Kotlin support type-polymorphism, which is enforced by Kotlin Serialization at +compile-time. For example, consider a generic serializable class `Box`: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +// The Box class can be used with built-in types like Int +// Alternatively, with user-defined types like Project +class Box(val contents: T) +@Serializable +data class Project(val name: String, val language: String) + +@Serializable +class Data( + val a: Box, + val b: Box +) + +fun main() { + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) + // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +When you serialize a generic class like `Box`, 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. + +## Customize serialization behavior + +Kotlin Serialization provides several ways to modify how classes are serialized. +This section covers techniques for customizing property names, managing default values, 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`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation. +Use it to make a property name shorter or more descriptive in the serialized output: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +// Abbreviates the language property to lang using @SerialName +class Project(val name: String, @SerialName("lang") val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + // Prints the abbreviated name in the JSON output + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","lang":"Kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +### 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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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 +``` +{kotlin-runnable="true"} + +### Validate data in the primary constructor + +You can validate constructor parameters before storing it in properties to ensure that a class is serializable and invalid data is rejected during deserialization. + +Use an `init` block to perform this validation: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":""} + """) + println(data) + // Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty +} +//sampleEnd +``` +{kotlin-runnable="true"} + +### Set default values for optional properties + +In Kotlin, an object can be deserialized only when all its properties are present in the input. + +You can set a _default value_ to a property to make it optional for serialization. +The default value is used when no value is provided in the input. + +Here's an example: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization"} + """) + println(data) + // Project(name=kotlinx.serialization, language=Kotlin) +} +//sampleEnd +``` +{kotlin-runnable="true"} + +### Make properties required with the `@Required` annotation + +You can annotate a property with [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) to make it required in the input. +This enforces that the input contains the property, even if it has a default value: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization"} + """) + println(data) + // MissingFieldException +} +//sampleEnd +``` +{kotlin-runnable="true"} + +### Exclude properties with the `@Transient` annotation + +You can exclude a property from serialization with the [`@Transient`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) + // JsonDecodingException +} +//sampleEnd +``` +{kotlin-runnable="true" validate="false"} + +> To avoid exceptions from unknown keys in JSON, including those marked with the `@Transient` annotation, enable the [`ignoreUnknownKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html) configuration property. +> For more information, see the [Ignore unknown keys](serialization-json-configuration.md#ignore-unknown-keys) section. +> +{style="tip"} + +### Manage the serialization of default properties with `@EncodedDefault` + +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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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 +``` +{kotlin-runnable="true"} + +> Learn more about configuring this JSON behavior in the [Encode default values](serialization-json-configuration.md#encode-default-values) section. +> +{style="tip"} + +To always serialize a property, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. +You can also change this behavior by setting the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. + +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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//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(EncodeDefault.Mode.NEVER) val projects: List = emptyList() +) + +fun main() { + val userA = User("Alice", listOf(Project("kotlinx.serialization"))) + val userB = User("Bob") + // Serializes projects because it contains a value + // language is always serialized + println(Json.encodeToString(userA)) + // {"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(userB)) + // {"name":"Bob"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +## What's next + +* Explore more complex JSON serialization scenarios in the [JSON serialization overview](configure-json-serialization.md). +* Learn how to define and customize your own serializers in [Create custom serializers](serialization-custom-serializers.md). From 372e4c97be069e32a19d81cfc56d0bc9b7b2d9b9 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Fri, 31 Oct 2025 15:31:53 +0100 Subject: [PATCH 2/5] update: implementing review comments --- .../serialization-customization-options.md | 373 +++++++++--------- 1 file changed, 189 insertions(+), 184 deletions(-) diff --git a/docs-website/topics/serialization-customization-options.md b/docs-website/topics/serialization-customization-options.md index 7a33aa69f..e21e43304 100644 --- a/docs-website/topics/serialization-customization-options.md +++ b/docs-website/topics/serialization-customization-options.md @@ -1,18 +1,17 @@ [//]: # (title: Serialize classes) -The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. +The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation enables the default serialization of all class properties with backing fields. You can further 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. -Before starting, make sure you have the [necessary library dependencies imported](serialization-get-started.md). +Before starting, make sure you've imported the [necessary library dependencies](serialization-get-started.md). ## The @Serializable annotation 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. -Properties defined with [accessors](properties.md) or delegated properties without backing fields are excluded from serialization: +In Kotlin, only properties with [backing fields](properties.md#backing-fields) are serialized: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -22,17 +21,17 @@ import kotlinx.serialization.json.* //sampleStart @Serializable class Project( - // Declares a property with a backing field - serialized + // Defines a property with a backing field – serialized var name: String ) { - // Declares a property with a backing field - serialized + // Defines a property with a backing field – serialized var stars: Int = 0 // Defines a getter-only property without a backing field - not serialized val path: String get() = "kotlin/$name" - // Declares a delegated property - not serialized + // Defines a delegated property - not serialized var id by ::name } @@ -46,8 +45,146 @@ fun main() { ``` {kotlin-runnable="true"} +### Serialization of class references + +Serializable classes 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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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 +``` +{kotlin-runnable="true"} + +> To reference non-serializable classes, annotate the corresponding properties with [`@Transient`](#exclude-properties-with-the-transient-annotation), or +> provide a [custom serializer](create-custom-serializers.md) for them. +> +{style="tip"} + +### 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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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 +``` +{kotlin-runnable="true"} + +> If you attempt to serialize a circular structure, it results in stack overflow. +> To exclude references from serialization, use [the @Transient annotation](#exclude-properties-with-the-transient-annotation). +> +{style="tip"} + +### Generic class serialization + +Generic classes in Kotlin support type-polymorphism, which is enforced by Kotlin Serialization at +compile-time. For example, consider a generic serializable class `Box`: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +// The Box class can be used with built-in types like Int +// or with @Serializable user-defined types like Project +class Box(val contents: T) +@Serializable +data class Project(val name: String, val language: String) + +@Serializable +class Data( + val a: Box, + val b: Box +) + +fun main() { + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) + // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +When you serialize a generic class like `Box`, 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 + +A property is optional if it isn't required in the serialized input or output. +Properties with _default values_ are optional during serialization. + +### 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: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization"} + """) + println(data) + // Project(name=kotlinx.serialization, language=Kotlin) +} +//sampleEnd +``` +{kotlin-runnable="true"} + +### Serialize nullable properties + Kotlin Serialization natively supports nullable properties. -Like [other _default values_](#set-default-values-for-optional-properties), `null` values aren't encoded in the JSON output: +Like other default values, `null` values aren't encoded in the JSON output: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -56,7 +193,7 @@ import kotlinx.serialization.json.* //sampleStart @Serializable -// Declares a class with the renamedTo nullable property that has a null default value +// 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() { @@ -97,7 +234,9 @@ fun main() { > {style="tip"} -Similarly, when the input contains an [optional property](#set-default-values-for-optional-properties) (a property with a default value), its initializer isn't called. +### Initializers in optional properties + +When the input contains an optional property, its initializer isn't called. For this reason, avoid using code with side effects in property initializers. Here's an example: @@ -131,46 +270,12 @@ fun main() { In this example, since the `language` property is specified in the input, the `Computing` string isn't printed in the output. -### Serialization of class references - -Serializable classes 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: - -```kotlin -// Imports declarations from the serialization and JSON handling libraries -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 -``` -{kotlin-runnable="true"} - -> To reference non-serializable classes, annotate the corresponding properties with [`@Transient`](#exclude-properties-with-the-transient-annotation), or -> provide a [custom serializer](create-custom-serializers.md) for them. -> -{style="tip"} +### Manage the serialization of default properties with `@EncodeDefault` -### Serialization of repeated object references +By default, JSON serialization excludes properties that have default values. +This reduces the size of the serialized data and avoids unnecessary visual clutter. -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 simply encoded twice: +Here's an example where the `language` property is excluded from the output because its value equals the default: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -179,31 +284,26 @@ import kotlinx.serialization.json.* //sampleStart @Serializable -class Project(val name: String, val owner: User, val maintainer: User) - -@Serializable -class User(val name: String) +data class Project(val name: String, val language: String = "Kotlin") fun main() { - val owner = User("kotlin") - // References owner twice - val data = Project("kotlinx.serialization", owner, owner) + val data = Project("kotlinx.serialization") println(Json.encodeToString(data)) - // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} + // {"name":"kotlinx.serialization"} } //sampleEnd ``` {kotlin-runnable="true"} -> If you attempt to serialize a circular structure, it results in stack overflow. -> To exclude references from serialization, use [the @Transient annotation](#exclude-properties-with-the-transient-annotation). +> Learn more about configuring this JSON behavior in the [Encode default values](serialization-json-configuration.md#encode-default-values) section. > {style="tip"} -### Generic class serialization +To always serialize a property, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. +You can also change this behavior by setting the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. -Generic classes in Kotlin support type-polymorphism, which is enforced by Kotlin Serialization at -compile-time. For example, consider a generic serializable class `Box`: +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: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -212,34 +312,41 @@ import kotlinx.serialization.json.* //sampleStart @Serializable -// The Box class can be used with built-in types like Int -// Alternatively, with user-defined types like Project -class Box(val contents: T) -@Serializable -data class Project(val name: String, val language: String) +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 -class Data( - val a: Box, - val b: Box +data class User( + val name: String, + // Excludes projects when it’s an empty list, even if it has a default value + @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() ) fun main() { - val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) - println(Json.encodeToString(data)) - // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} + val userA = User("Alice", listOf(Project("kotlinx.serialization"))) + val userB = User("Bob") + // Serializes projects because it contains a value + // language is always serialized + println(Json.encodeToString(userA)) + // {"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(userB)) + // {"name":"Bob"} } //sampleEnd ``` {kotlin-runnable="true"} -When you serialize a generic class like `Box`, 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. - -## Customize serialization behavior +## Customize class serialization Kotlin Serialization provides several ways to modify how classes are serialized. -This section covers techniques for customizing property names, managing default values, and more. +This section covers techniques for customizing property names, controlling property inclusion, and more. ### Customize serial names @@ -247,7 +354,7 @@ By default, property names in the serialization output, such as JSON, match thei You can customize these names, called _serial names_, with the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation. -Use it to make a property name shorter or more descriptive in the serialized output: +Use it to make a property name more descriptive in the serialized output: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -305,9 +412,10 @@ fun main() { ### Validate data in the primary constructor -You can validate constructor parameters before storing it in properties to ensure that a class is serializable and invalid data is rejected during deserialization. +To ensure that a class remains serializable and that invalid data is rejected during deserialization, add validation checks in an `init` block. +During deserialization, Kotlin calls the class’s primary constructor and runs all initializer blocks. -Use an `init` block to perform this validation: +Here's an example: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -334,40 +442,10 @@ fun main() { ``` {kotlin-runnable="true"} -### Set default values for optional properties - -In Kotlin, an object can be deserialized only when all its properties are present in the input. - -You can set a _default value_ to a property to make it optional for serialization. -The default value is used when no value is provided in the input. - -Here's an example: - -```kotlin -// Imports declarations from the serialization and JSON handling libraries -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(""" - {"name":"kotlinx.serialization"} - """) - println(data) - // Project(name=kotlinx.serialization, language=Kotlin) -} -//sampleEnd -``` -{kotlin-runnable="true"} - ### Make properties required with the `@Required` annotation You can annotate a property with [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) to make it required in the input. -This enforces that the input contains the property, even if it has a default value: +This enforces that the input contains the property, even if it has a [default value](#set-default-values-for-optional-properties): ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -426,79 +504,6 @@ fun main() { > {style="tip"} -### Manage the serialization of default properties with `@EncodedDefault` - -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: - -```kotlin -// Imports declarations from the serialization and JSON handling libraries -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 -``` -{kotlin-runnable="true"} - -> Learn more about configuring this JSON behavior in the [Encode default values](serialization-json-configuration.md#encode-default-values) section. -> -{style="tip"} - -To always serialize a property, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. -You can also change this behavior by setting the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. - -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: - -```kotlin -// Imports declarations from the serialization and JSON handling libraries -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -//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(EncodeDefault.Mode.NEVER) val projects: List = emptyList() -) - -fun main() { - val userA = User("Alice", listOf(Project("kotlinx.serialization"))) - val userB = User("Bob") - // Serializes projects because it contains a value - // language is always serialized - println(Json.encodeToString(userA)) - // {"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(userB)) - // {"name":"Bob"} -} -//sampleEnd -``` -{kotlin-runnable="true"} - ## What's next * Explore more complex JSON serialization scenarios in the [JSON serialization overview](configure-json-serialization.md). From 3905df96a975beb89714be9c80c66dc32c6b9c47 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Tue, 4 Nov 2025 15:30:41 +0100 Subject: [PATCH 3/5] update: implementing additional review comments --- .../serialization-customization-options.md | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/docs-website/topics/serialization-customization-options.md b/docs-website/topics/serialization-customization-options.md index e21e43304..41e339c76 100644 --- a/docs-website/topics/serialization-customization-options.md +++ b/docs-website/topics/serialization-customization-options.md @@ -129,6 +129,7 @@ import kotlinx.serialization.json.* // The Box class can be used with built-in types like Int // or with @Serializable user-defined types like Project class Box(val contents: T) + @Serializable data class Project(val name: String, val language: String) @@ -152,8 +153,9 @@ If that type isn't serializable, you get a compile-time error. ## Optional properties -A property is optional if it isn't required in the serialized input or output. -Properties with _default values_ are optional during serialization. +Properties with _default values_ are optional during deserialization, and can be skipped during serialization, +if the format is configured to do so. +For example, JSON is configured to skip encoding of default values out of the box. ### Set default values for optional properties @@ -184,7 +186,7 @@ fun main() { ### Serialize nullable properties Kotlin Serialization natively supports nullable properties. -Like other default values, `null` values aren't encoded in the JSON output: +[Like other default values](#manage-the-serialization-of-default-properties-with-encodedefault), `null` values aren't encoded in the JSON output: ```kotlin // Imports declarations from the serialization and JSON handling libraries @@ -231,6 +233,8 @@ fun main() { {kotlin-runnable="true" validate="false"} > If you need to handle `null` values from third-party JSON, you can [coerce them to a default value](serialization-json-configuration.md#coerce-input-values). +> +> You can also [omit explicit `null` values](serialization-json-configuration#omit-explicit-nulls) from the encoded JSON with the `explicitNulls` property. > {style="tip"} @@ -343,6 +347,33 @@ fun main() { ``` {kotlin-runnable="true"} +### Make properties required with the `@Required` annotation + +You can annotate a property with [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) to make it required in the input. +This enforces that the input contains the property, even if it has a [default value](#set-default-values-for-optional-properties): + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +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(""" + {"name":"kotlinx.serialization"} + """) + println(data) + // MissingFieldException +} +//sampleEnd +``` +{kotlin-runnable="true"} + ## Customize class serialization Kotlin Serialization provides several ways to modify how classes are serialized. @@ -412,8 +443,9 @@ fun main() { ### Validate data in the primary constructor -To ensure that a class remains serializable and that invalid data is rejected during deserialization, add validation checks in an `init` block. -During deserialization, Kotlin calls the class’s primary constructor and runs all initializer blocks. +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: @@ -442,33 +474,6 @@ fun main() { ``` {kotlin-runnable="true"} -### Make properties required with the `@Required` annotation - -You can annotate a property with [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) to make it required in the input. -This enforces that the input contains the property, even if it has a [default value](#set-default-values-for-optional-properties): - -```kotlin -// Imports declarations from the serialization and JSON handling libraries -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(""" - {"name":"kotlinx.serialization"} - """) - println(data) - // MissingFieldException -} -//sampleEnd -``` -{kotlin-runnable="true"} - ### Exclude properties with the `@Transient` annotation You can exclude a property from serialization with the [`@Transient`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/) annotation. From bfee2a3bbffbf4f89e81f1d9d746f6526ba6234e Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Tue, 4 Nov 2025 17:13:26 +0100 Subject: [PATCH 4/5] update: small code clean up and adding experimental API information --- .../topics/serialization-customization-options.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs-website/topics/serialization-customization-options.md b/docs-website/topics/serialization-customization-options.md index 41e339c76..9a7b9dae4 100644 --- a/docs-website/topics/serialization-customization-options.md +++ b/docs-website/topics/serialization-customization-options.md @@ -275,6 +275,7 @@ In this example, since the `language` property is specified in the input, the `C 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. @@ -306,13 +307,21 @@ fun main() { To always serialize a property, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. You can also change this behavior by setting the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. +> [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/) is [Experimental](components-stability.md#stability-levels-explained). To opt in, use the `@OptIn(ExperimentalSerializationApi::class)` annotation or the compiler option `-opt-in=kotlinx.serialization.ExperimentalSerializationApi`. +> +{style="warning"} + 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: ```kotlin +// Opts in to ExperimentalSerializationApi +@file:OptIn(ExperimentalSerializationApi::class) + // Imports declarations from the serialization and JSON handling libraries import kotlinx.serialization.* import kotlinx.serialization.json.* +import kotlinx.serialization.EncodeDefault.Mode.NEVER //sampleStart @Serializable @@ -327,7 +336,7 @@ data class Project( data class User( val name: String, // Excludes projects when it’s an empty list, even if it has a default value - @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() + @EncodeDefault(NEVER) val projects: List = emptyList() ) fun main() { From 638032e1a53bf6b17aa542966762b5bcdd18084f Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 6 Nov 2025 09:27:23 +0100 Subject: [PATCH 5/5] update: removing experimental and adding some finishing touches --- .../serialization-customization-options.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docs-website/topics/serialization-customization-options.md b/docs-website/topics/serialization-customization-options.md index 9a7b9dae4..a69aed03e 100644 --- a/docs-website/topics/serialization-customization-options.md +++ b/docs-website/topics/serialization-customization-options.md @@ -21,17 +21,17 @@ import kotlinx.serialization.json.* //sampleStart @Serializable class Project( - // Defines a property with a backing field – serialized + // Property with a backing field – serialized var name: String ) { - // Defines a property with a backing field – serialized + // Property with a backing field – serialized var stars: Int = 0 - // Defines a getter-only property without a backing field - not serialized + // Getter-only property without a backing field - not serialized val path: String get() = "kotlin/$name" - // Defines a delegated property - not serialized + // Delegated property - not serialized var id by ::name } @@ -275,7 +275,6 @@ In this example, since the `language` property is specified in the input, the `C 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. @@ -300,24 +299,17 @@ fun main() { ``` {kotlin-runnable="true"} -> Learn more about configuring this JSON behavior in the [Encode default values](serialization-json-configuration.md#encode-default-values) section. +> You can configure a `Json` instance to [encode default values](serialization-json-configuration.md#encode-default-values) for all properties by default. > {style="tip"} To always serialize a property, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. You can also change this behavior by setting the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. -> [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/) is [Experimental](components-stability.md#stability-levels-explained). To opt in, use the `@OptIn(ExperimentalSerializationApi::class)` annotation or the compiler option `-opt-in=kotlinx.serialization.ExperimentalSerializationApi`. -> -{style="warning"} - 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: ```kotlin -// Opts in to ExperimentalSerializationApi -@file:OptIn(ExperimentalSerializationApi::class) - // Imports declarations from the serialization and JSON handling libraries import kotlinx.serialization.* import kotlinx.serialization.json.*