xref: /aosp_15_r20/external/kotlinx.serialization/docs/basic-serialization.md (revision 57b5a4a64c534cf7f27ac9427ceab07f3d8ed3d8)
1<!--- TEST_NAME BasicSerializationTest -->
2
3# Basic Serialization
4
5This is the first chapter of the [Kotlin Serialization Guide](serialization-guide.md).
6This chapter shows the basic use of Kotlin Serialization and explains its core concepts.
7
8**Table of contents**
9
10<!--- TOC -->
11
12* [Basics](#basics)
13  * [JSON encoding](#json-encoding)
14  * [JSON decoding](#json-decoding)
15* [Serializable classes](#serializable-classes)
16  * [Backing fields are serialized](#backing-fields-are-serialized)
17  * [Constructor properties requirement](#constructor-properties-requirement)
18  * [Data validation](#data-validation)
19  * [Optional properties](#optional-properties)
20  * [Optional property initializer call](#optional-property-initializer-call)
21  * [Required properties](#required-properties)
22  * [Transient properties](#transient-properties)
23  * [Defaults are not encoded by default](#defaults-are-not-encoded-by-default)
24  * [Nullable properties](#nullable-properties)
25  * [Type safety is enforced](#type-safety-is-enforced)
26  * [Referenced objects](#referenced-objects)
27  * [No compression of repeated references](#no-compression-of-repeated-references)
28  * [Generic classes](#generic-classes)
29  * [Serial field names](#serial-field-names)
30
31<!--- END -->
32
33## Basics
34
35To convert an object tree to a string or to a sequence of bytes, it must come
36through two mutually intertwined processes. In the first step, an object is _serialized_&mdash;it
37is converted into a serial sequence of its constituting primitive values. This process is common for all
38data formats and its result depends on the object being serialized. A _serializer_ controls this process.
39The second step is called _encoding_&mdash;it is the conversion of the corresponding sequence of primitives into
40the output format representation. An _encoder_ controls this process. Whenever the distinction is not important,
41both the terms of encoding and serialization are used interchangeably.
42
43```
44+---------+  Serialization  +------------+  Encoding  +---------------+
45| Objects | --------------> | Primitives | ---------> | Output format |
46+---------+                 +------------+            +---------------+
47```
48
49The reverse process starts with parsing of the input format and _decoding_ of primitive values,
50followed by _deserialization_ of the resulting stream into objects. We'll see details of this process later.
51
52For now, we start with [JSON](https://json.org) encoding.
53
54<!--- INCLUDE .*-basic-.*
55import kotlinx.serialization.*
56import kotlinx.serialization.json.*
57-->
58
59### JSON encoding
60
61The whole process of converting data into a specific format is called _encoding_. For JSON we encode data
62using the [Json.encodeToString][kotlinx.serialization.encodeToString] extension function. It serializes
63the object that is passed as its parameter under the hood and encodes it to a JSON string.
64
65Let's start with a class describing a project and try to get its JSON representation.
66
67```kotlin
68class Project(val name: String, val language: String)
69
70fun main() {
71    val data = Project("kotlinx.serialization", "Kotlin")
72    println(Json.encodeToString(data))
73}
74```
75
76> You can get the full code [here](../guide/example/example-basic-01.kt).
77
78When we run this code we get the exception.
79
80```text
81Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.
82Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
83```
84
85<!--- TEST LINES_START -->
86
87Serializable classes have to be explicitly marked. Kotlin Serialization does not use reflection,
88so you cannot accidentally deserialize a class which was not supposed to be serializable. We fix it by
89adding the [`@Serializable`][Serializable] annotation.
90
91```kotlin
92@Serializable
93class Project(val name: String, val language: String)
94
95fun main() {
96    val data = Project("kotlinx.serialization", "Kotlin")
97    println(Json.encodeToString(data))
98}
99```
100
101> You can get the full code [here](../guide/example/example-basic-02.kt).
102
103The `@Serializable` annotation instructs the Kotlin Serialization plugin to automatically generate and hook
104up a _serializer_ for this class. Now the output of the example is the corresponding JSON.
105
106```text
107{"name":"kotlinx.serialization","language":"Kotlin"}
108```
109
110<!--- TEST -->
111
112> There is a whole chapter about the [Serializers](serializers.md). For now, it is enough to know
113> that they are automatically generated by the Kotlin Serialization plugin.
114
115### JSON decoding
116
117The reverse process is called _decoding_. To decode a JSON string into an object, we'll
118use the [Json.decodeFromString][kotlinx.serialization.decodeFromString] extension function.
119To specify which type we want to get as a result, we provide a type parameter to this function.
120
121As we'll see later, serialization works with different kinds of classes.
122Here we are marking our `Project` class as a `data class`, not because it is required, but because
123we want to print its contents to verify how it decodes.
124
125```kotlin
126@Serializable
127data class Project(val name: String, val language: String)
128
129fun main() {
130    val data = Json.decodeFromString<Project>("""
131        {"name":"kotlinx.serialization","language":"Kotlin"}
132    """)
133    println(data)
134}
135```
136
137> You can get the full code [here](../guide/example/example-basic-03.kt).
138
139Running this code we get back the object.
140
141```text
142Project(name=kotlinx.serialization, language=Kotlin)
143```
144
145<!--- TEST -->
146
147## Serializable classes
148
149This section goes into more details on how different `@Serializable` classes are handled.
150
151<!--- INCLUDE .*-classes-.*
152import kotlinx.serialization.*
153import kotlinx.serialization.json.*
154-->
155
156### Backing fields are serialized
157
158Only a class's properties with backing fields are serialized, so properties with a getter/setter that don't
159have a backing field and delegated properties are not serialized, as the following example shows.
160
161```kotlin
162@Serializable
163class Project(
164    // name is a property with backing field -- serialized
165    var name: String
166) {
167    var stars: Int = 0 // property with a backing field -- serialized
168
169    val path: String // getter only, no backing field -- not serialized
170        get() = "kotlin/$name"
171
172    var id by ::name // delegated property -- not serialized
173}
174
175fun main() {
176    val data = Project("kotlinx.serialization").apply { stars = 9000 }
177    println(Json.encodeToString(data))
178}
179```
180
181> You can get the full code [here](../guide/example/example-classes-01.kt).
182
183We can clearly see that only the `name` and `stars` properties are present in the JSON output.
184
185```text
186{"name":"kotlinx.serialization","stars":9000}
187```
188
189<!--- TEST -->
190
191### Constructor properties requirement
192
193If we want to define the `Project` class so that it takes a path string, and then
194deconstructs it into the corresponding properties, we might be tempted to write something like the code below.
195
196```kotlin
197@Serializable
198class Project(path: String) {
199    val owner: String = path.substringBefore('/')
200    val name: String = path.substringAfter('/')
201}
202```
203
204<!--- CLEAR -->
205
206This class does not compile because the `@Serializable` annotation requires that all parameters of the class's primary
207constructor be properties. A simple workaround is to define a private primary constructor with the class's
208properties, and turn the constructor we wanted into the secondary one.
209
210```kotlin
211@Serializable
212class Project private constructor(val owner: String, val name: String) {
213    constructor(path: String) : this(
214        owner = path.substringBefore('/'),
215        name = path.substringAfter('/')
216    )
217
218    val path: String
219        get() = "$owner/$name"
220}
221```
222
223Serialization works with a private primary constructor, and still serializes only backing fields.
224
225```kotlin
226fun main() {
227    println(Json.encodeToString(Project("kotlin/kotlinx.serialization")))
228}
229```
230
231> You can get the full code [here](../guide/example/example-classes-02.kt).
232
233This example produces the expected output.
234
235```text
236{"owner":"kotlin","name":"kotlinx.serialization"}
237```
238
239<!--- TEST -->
240
241### Data validation
242
243Another case where you might want to introduce a primary constructor parameter without a property is when you
244want to validate its value before storing it to a property. To make it serializable you shall replace it
245with a property in the primary constructor, and move the validation to an `init { ... }` block.
246
247```kotlin
248@Serializable
249class Project(val name: String) {
250    init {
251        require(name.isNotEmpty()) { "name cannot be empty" }
252    }
253}
254```
255
256A deserialization process works like a regular constructor in Kotlin and calls all `init` blocks, ensuring that you
257cannot get an invalid class as a result of deserialization. Let's try it.
258
259```kotlin
260fun main() {
261    val data = Json.decodeFromString<Project>("""
262        {"name":""}
263    """)
264    println(data)
265}
266```
267
268> You can get the full code [here](../guide/example/example-classes-03.kt).
269
270Running this code produces the exception:
271
272```text
273Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty
274```
275
276<!--- TEST LINES_START -->
277
278### Optional properties
279
280An object can be deserialized only when all its properties are present in the input.
281For example, run the following code.
282
283```kotlin
284@Serializable
285data class Project(val name: String, val language: String)
286
287fun main() {
288    val data = Json.decodeFromString<Project>("""
289        {"name":"kotlinx.serialization"}
290    """)
291    println(data)
292}
293```
294
295> You can get the full code [here](../guide/example/example-classes-04.kt).
296
297It produces the exception:
298
299```text
300Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $
301```
302
303<!--- TEST LINES_START -->
304
305This problem can be fixed by adding a default value to the property, which automatically makes it optional
306for serialization.
307
308```kotlin
309@Serializable
310data class Project(val name: String, val language: String = "Kotlin")
311
312fun main() {
313    val data = Json.decodeFromString<Project>("""
314        {"name":"kotlinx.serialization"}
315    """)
316    println(data)
317}
318```
319
320> You can get the full code [here](../guide/example/example-classes-05.kt).
321
322It produces the following output with the default value for the `language` property.
323
324```text
325Project(name=kotlinx.serialization, language=Kotlin)
326```
327
328<!--- TEST -->
329
330### Optional property initializer call
331
332When an optional property is present in the input, the corresponding initializer for this
333property is not even called. This is a feature designed for performance, so be careful not
334to rely on side effects in initializers. Consider the example below.
335
336```kotlin
337fun computeLanguage(): String {
338    println("Computing")
339    return "Kotlin"
340}
341
342@Serializable
343data class Project(val name: String, val language: String = computeLanguage())
344
345fun main() {
346    val data = Json.decodeFromString<Project>("""
347        {"name":"kotlinx.serialization","language":"Kotlin"}
348    """)
349    println(data)
350}
351```
352
353> You can get the full code [here](../guide/example/example-classes-06.kt).
354
355Since the `language` property was specified in the input, we don't see the "Computing" string printed
356in the output.
357
358```text
359Project(name=kotlinx.serialization, language=Kotlin)
360```
361
362<!--- TEST -->
363
364### Required properties
365
366A property with a default value can be required in a serial format with the [`@Required`][Required] annotation.
367Let us change the previous example by marking the `language` property as `@Required`.
368
369```kotlin
370@Serializable
371data class Project(val name: String, @Required val language: String = "Kotlin")
372
373fun main() {
374    val data = Json.decodeFromString<Project>("""
375        {"name":"kotlinx.serialization"}
376    """)
377    println(data)
378}
379```
380
381> You can get the full code [here](../guide/example/example-classes-07.kt).
382
383We get the following exception.
384
385```text
386Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $
387```
388
389<!--- TEST LINES_START -->
390
391### Transient properties
392
393A property can be excluded from serialization by marking it with the [`@Transient`][Transient] annotation
394(don't confuse it with [kotlin.jvm.Transient]). Transient properties must have a default value.
395
396```kotlin
397@Serializable
398data class Project(val name: String, @Transient val language: String = "Kotlin")
399
400fun main() {
401    val data = Json.decodeFromString<Project>("""
402        {"name":"kotlinx.serialization","language":"Kotlin"}
403    """)
404    println(data)
405}
406```
407
408> You can get the full code [here](../guide/example/example-classes-08.kt).
409
410Attempts to explicitly specify its value in the serial format, even if the specified
411value is equal to the default one, produces the following exception.
412
413```text
414Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name
415Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
416```
417
418<!--- TEST LINES_START -->
419
420> The 'ignoreUnknownKeys' feature is explained in the [Ignoring Unknown Keys section](json.md#ignoring-unknown-keys) section.
421
422### Defaults are not encoded by default
423
424Default values are not encoded by default in JSON. This behavior is motivated by the fact that in most real-life scenarios
425such configuration reduces visual clutter, and saves the amount of data being serialized.
426
427```kotlin
428@Serializable
429data class Project(val name: String, val language: String = "Kotlin")
430
431fun main() {
432    val data = Project("kotlinx.serialization")
433    println(Json.encodeToString(data))
434}
435```
436
437> You can get the full code [here](../guide/example/example-classes-09.kt).
438
439It produces the following output, which does not have the `language` property because its value is equal to the default one.
440
441```text
442{"name":"kotlinx.serialization"}
443```
444
445<!--- TEST -->
446
447See JSON's [Encoding defaults](json.md#encoding-defaults) section on how this behavior can be configured for JSON.
448Additionally, this behavior can be controlled without taking format settings into account.
449For that purposes, [EncodeDefault] annotation can be used:
450
451```kotlin
452@Serializable
453data class Project(
454    val name: String,
455    @EncodeDefault val language: String = "Kotlin"
456)
457```
458
459This annotation instructs the framework to always serialize property, regardless of its value or format settings.
460It's also possible to tweak it into the opposite behavior using [EncodeDefault.Mode] parameter:
461
462```kotlin
463
464@Serializable
465data class User(
466    val name: String,
467    @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List<Project> = emptyList()
468)
469
470fun main() {
471    val userA = User("Alice", listOf(Project("kotlinx.serialization")))
472    val userB = User("Bob")
473    println(Json.encodeToString(userA))
474    println(Json.encodeToString(userB))
475}
476```
477
478> You can get the full code [here](../guide/example/example-classes-10.kt).
479
480As you can see, `language` property is preserved and `projects` is omitted:
481
482```text
483{"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]}
484{"name":"Bob"}
485```
486
487<!--- TEST -->
488
489### Nullable properties
490
491Nullable properties are natively supported by Kotlin Serialization.
492
493```kotlin
494@Serializable
495class Project(val name: String, val renamedTo: String? = null)
496
497fun main() {
498    val data = Project("kotlinx.serialization")
499    println(Json.encodeToString(data))
500}
501```
502
503> You can get the full code [here](../guide/example/example-classes-11.kt).
504
505This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded).
506
507```text
508{"name":"kotlinx.serialization"}
509```
510
511<!--- TEST -->
512
513### Type safety is enforced
514
515Kotlin Serialization strongly enforces the type safety of the Kotlin programming language.
516In particular, let us try to decode a `null` value from a JSON object into a non-nullable Kotlin property `language`.
517
518```kotlin
519@Serializable
520data class Project(val name: String, val language: String = "Kotlin")
521
522fun main() {
523    val data = Json.decodeFromString<Project>("""
524        {"name":"kotlinx.serialization","language":null}
525    """)
526    println(data)
527}
528```
529
530> You can get the full code [here](../guide/example/example-classes-12.kt).
531
532Even though the `language` property has a default value, it is still an error to attempt to assign
533the `null` value to it.
534
535```text
536Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language
537Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value.
538```
539
540<!--- TEST LINES_START -->
541
542> It might be desired, when decoding 3rd-party JSONs, to coerce `null` to a default value.
543> The corresponding feature is explained in the [Coercing input values](json.md#coercing-input-values) section.
544
545### Referenced objects
546
547Serializable classes can reference other classes in their serializable properties.
548The referenced classes must be also marked as `@Serializable`.
549
550```kotlin
551@Serializable
552class Project(val name: String, val owner: User)
553
554@Serializable
555class User(val name: String)
556
557fun main() {
558    val owner = User("kotlin")
559    val data = Project("kotlinx.serialization", owner)
560    println(Json.encodeToString(data))
561}
562```
563
564> You can get the full code [here](../guide/example/example-classes-13.kt).
565
566When encoded to JSON it results in a nested JSON object.
567
568```text
569{"name":"kotlinx.serialization","owner":{"name":"kotlin"}}
570```
571
572> References to non-serializable classes can be marked as [Transient properties](#transient-properties), or a
573> custom serializer can be provided for them as shown in the [Serializers](serializers.md) chapter.
574
575<!--- TEST -->
576
577### No compression of repeated references
578
579Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction
580of arbitrary object graphs with repeated object references. For example, let us try to serialize an object
581that references the same `owner` instance twice.
582
583```kotlin
584@Serializable
585class Project(val name: String, val owner: User, val maintainer: User)
586
587@Serializable
588class User(val name: String)
589
590fun main() {
591    val owner = User("kotlin")
592    val data = Project("kotlinx.serialization", owner, owner)
593    println(Json.encodeToString(data))
594}
595```
596
597> You can get the full code [here](../guide/example/example-classes-14.kt).
598
599We simply get the `owner` value encoded twice.
600
601```text
602{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}}
603```
604
605> Attempt to serialize a circular structure will result in stack overflow.
606> You can use the [Transient properties](#transient-properties) to exclude some references from serialization.
607
608<!--- TEST -->
609
610### Generic classes
611
612Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at
613compile-time. For example, consider a generic serializable class `Box<T>`.
614
615```kotlin
616@Serializable
617class Box<T>(val contents: T)
618```
619
620The `Box<T>` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`.
621
622<!--- INCLUDE
623
624@Serializable
625data class Project(val name: String, val language: String)
626-->
627
628```kotlin
629@Serializable
630class Data(
631    val a: Box<Int>,
632    val b: Box<Project>
633)
634
635fun main() {
636    val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
637    println(Json.encodeToString(data))
638}
639```
640
641> You can get the full code [here](../guide/example/example-classes-15.kt).
642
643The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`.
644
645```text
646{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}}
647```
648
649<!--- TEST -->
650
651If the actual generic type is not serializable a compile-time error will be produced.
652
653### Serial field names
654
655The names of the properties used in encoded representation, JSON in our examples, are the same as
656their names in the source code by default. The name that is used for serialization is called a _serial name_, and
657can be changed using the [`@SerialName`][SerialName] annotation. For example, we can have a `language` property in
658the source with an abbreviated serial name.
659
660```kotlin
661@Serializable
662class Project(val name: String, @SerialName("lang") val language: String)
663
664fun main() {
665    val data = Project("kotlinx.serialization", "Kotlin")
666    println(Json.encodeToString(data))
667}
668```
669
670> You can get the full code [here](../guide/example/example-classes-16.kt).
671
672Now we see that an abbreviated name `lang` is used in the JSON output.
673
674```text
675{"name":"kotlinx.serialization","lang":"Kotlin"}
676```
677
678<!--- TEST -->
679
680---
681
682The next chapter covers [Builtin classes](builtin-classes.md).
683
684<!-- stdlib references -->
685[kotlin.jvm.Transient]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-transient/
686
687<!--- MODULE /kotlinx-serialization-core -->
688<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
689
690[kotlinx.serialization.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html
691[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
692[kotlinx.serialization.decodeFromString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html
693[Required]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/index.html
694[Transient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html
695[EncodeDefault]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/index.html
696[EncodeDefault.Mode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/index.html
697[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
698
699<!--- MODULE /kotlinx-serialization-json -->
700<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
701<!--- END -->
702