summaryrefslogtreecommitdiff
path: root/docs/basic-serialization.md
blob: 96e709817d2b5b69fe6748403538123ae4ce220a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
<!--- TEST_NAME BasicSerializationTest -->

# Basic Serialization

This is the first chapter of the [Kotlin Serialization Guide](serialization-guide.md).
This chapter shows the basic use of Kotlin Serialization and explains its core concepts.

**Table of contents**

<!--- TOC -->

* [Basics](#basics)
  * [JSON encoding](#json-encoding)
  * [JSON decoding](#json-decoding)
* [Serializable classes](#serializable-classes)
  * [Backing fields are serialized](#backing-fields-are-serialized)
  * [Constructor properties requirement](#constructor-properties-requirement)
  * [Data validation](#data-validation)
  * [Optional properties](#optional-properties)
  * [Optional property initializer call](#optional-property-initializer-call)
  * [Required properties](#required-properties)
  * [Transient properties](#transient-properties)
  * [Defaults are not encoded by default](#defaults-are-not-encoded-by-default)
  * [Nullable properties](#nullable-properties)
  * [Type safety is enforced](#type-safety-is-enforced)
  * [Referenced objects](#referenced-objects)
  * [No compression of repeated references](#no-compression-of-repeated-references)
  * [Generic classes](#generic-classes)
  * [Serial field names](#serial-field-names)

<!--- END -->

## Basics

To convert an object tree to a string or to a sequence of bytes, it must come
through two mutually intertwined processes. In the first step, an object is _serialized_&mdash;it
is converted into a serial sequence of its constituting primitive values. This process is common for all
data formats and its result depends on the object being serialized. A _serializer_ controls this process.
The second step is called _encoding_&mdash;it is the conversion of the corresponding sequence of primitives into
the output format representation. An _encoder_ controls this process. Whenever the distinction is not important,
both the terms of encoding and serialization are used interchangeably.

```
+---------+  Serialization  +------------+  Encoding  +---------------+
| Objects | --------------> | Primitives | ---------> | Output format |
+---------+                 +------------+            +---------------+
```

The reverse process starts with parsing of the input format and _decoding_ of primitive values,
followed by _deserialization_ of the resulting stream into objects. We'll see details of this process later.

For now, we start with [JSON](https://json.org) encoding.

<!--- INCLUDE .*-basic-.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-->

### JSON encoding

The whole process of converting data into a specific format is called _encoding_. For JSON we encode data
using the [Json.encodeToString][kotlinx.serialization.encodeToString] extension function. It serializes
the object that is passed as its parameter under the hood and encodes it to a JSON string.

Let's start with a class describing a project and try to get its JSON representation.

```kotlin
class Project(val name: String, val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-basic-01.kt).

When we run this code we get the exception.

```text
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
```

<!--- TEST LINES_START -->

Serializable classes have to be explicitly marked. Kotlin Serialization does not use reflection,
so you cannot accidentally deserialize a class which was not supposed to be serializable. We fix it by
adding the [`@Serializable`][Serializable] annotation.

```kotlin
@Serializable
class Project(val name: String, val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-basic-02.kt).

The `@Serializable` annotation instructs the Kotlin Serialization plugin to automatically generate and hook
up a _serializer_ for this class. Now the output of the example is the corresponding JSON.

```text
{"name":"kotlinx.serialization","language":"Kotlin"}
```

<!--- TEST -->

> There is a whole chapter about the [Serializers](serializers.md). For now, it is enough to know
> that they are automatically generated by the Kotlin Serialization plugin.

### JSON decoding

The reverse process is called _decoding_. To decode a JSON string into an object, we'll
use the [Json.decodeFromString][kotlinx.serialization.decodeFromString] extension function.
To specify which type we want to get as a result, we provide a type parameter to this function.

As we'll see later, serialization works with different kinds of classes.
Here we are marking our `Project` class as a `data class`, not because it is required, but because
we want to print its contents to verify how it decodes.

```kotlin
@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-basic-03.kt).

Running this code we get back the object.

```text
Project(name=kotlinx.serialization, language=Kotlin)
```

<!--- TEST -->

## Serializable classes

This section goes into more details on how different `@Serializable` classes are handled.

<!--- INCLUDE .*-classes-.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-->

### Backing fields are serialized

Only a class's properties with backing fields are serialized, so properties with a getter/setter that don't
have a backing field and delegated properties are not serialized, as the following example shows.

```kotlin
@Serializable
class Project(
    // name is a property with backing field -- serialized
    var name: String
) {
    var stars: Int = 0 // property with a backing field -- serialized

    val path: String // getter only, no backing field -- not serialized
        get() = "kotlin/$name"

    var id by ::name // delegated property -- not serialized
}

fun main() {
    val data = Project("kotlinx.serialization").apply { stars = 9000 }
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-01.kt).

We can clearly see that only the `name` and `stars` properties are present in the JSON output.

```text
{"name":"kotlinx.serialization","stars":9000}
```

<!--- TEST -->

### Constructor properties requirement

If we want to define the `Project` class so that it takes a path string, and then
deconstructs it into the corresponding properties, we might be tempted to write something like the code below.

```kotlin
@Serializable
class Project(path: String) {
    val owner: String = path.substringBefore('/')
    val name: String = path.substringAfter('/')
}
```

<!--- CLEAR -->

This class does not compile because the `@Serializable` annotation requires that all parameters of the class's primary
constructor be properties. A simple workaround is to define a private primary constructor with the class's
properties, and turn the constructor we wanted into the secondary one.

```kotlin
@Serializable
class Project private constructor(val owner: String, val name: String) {
    constructor(path: String) : this(
        owner = path.substringBefore('/'),
        name = path.substringAfter('/')
    )

    val path: String
        get() = "$owner/$name"
}
```

Serialization works with a private primary constructor, and still serializes only backing fields.

```kotlin
fun main() {
    println(Json.encodeToString(Project("kotlin/kotlinx.serialization")))
}
```

> You can get the full code [here](../guide/example/example-classes-02.kt).

This example produces the expected output.

```text
{"owner":"kotlin","name":"kotlinx.serialization"}
```

<!--- TEST -->

### Data validation

Another case where you might want to introduce a primary constructor parameter without a property is when you
want to validate its value before storing it to a property. To make it serializable you shall replace it
with a property in the primary constructor, and move the validation to an `init { ... }` block.

```kotlin
@Serializable
class Project(val name: String) {
    init {
        require(name.isNotEmpty()) { "name cannot be empty" }
    }
}
```

A deserialization process works like a regular constructor in Kotlin and calls all `init` blocks, ensuring that you
cannot get an invalid class as a result of deserialization. Let's try it.

```kotlin
fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":""}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-classes-03.kt).

Running this code produces the exception:

```text
Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty
```

<!--- TEST LINES_START -->

### Optional properties

An object can be deserialized only when all its properties are present in the input.
For example, run the following code.

```kotlin
@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-classes-04.kt).

It produces the exception:

```text
Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $
```

<!--- TEST LINES_START -->

This problem can be fixed by adding a default value to the property, which automatically makes it optional
for serialization.

```kotlin
@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-classes-05.kt).

It produces the following output with the default value for the `language` property.

```text
Project(name=kotlinx.serialization, language=Kotlin)
```

<!--- TEST -->

### Optional property initializer call

When an optional property is present in the input, the corresponding initializer for this
property is not even called. This is a feature designed for performance, so be careful not
to rely on side effects in initializers. Consider the example below.

```kotlin
fun computeLanguage(): String {
    println("Computing")
    return "Kotlin"
}

@Serializable
data class Project(val name: String, val language: String = computeLanguage())

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-classes-06.kt).

Since the `language` property was specified in the input, we don't see the "Computing" string printed
in the output.

```text
Project(name=kotlinx.serialization, language=Kotlin)
```

<!--- TEST -->

### Required properties

A property with a default value can be required in a serial format with the [`@Required`][Required] annotation.
Let us change the previous example by marking the `language` property as `@Required`.

```kotlin
@Serializable
data class Project(val name: String, @Required val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-classes-07.kt).

We get the following exception.

```text
Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $
```

<!--- TEST LINES_START -->

### Transient properties

A property can be excluded from serialization by marking it with the [`@Transient`][Transient] annotation
(don't confuse it with [kotlin.jvm.Transient]). Transient properties must have a default value.

```kotlin
@Serializable
data class Project(val name: String, @Transient val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}
```

> You can get the full code [here](../guide/example/example-classes-08.kt).

Attempts to explicitly specify its value in the serial format, even if the specified
value is equal to the default one, produces the following exception.

```text
Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
```

<!--- TEST LINES_START -->

> The 'ignoreUnknownKeys' feature is explained in the [Ignoring Unknown Keys section](json.md#ignoring-unknown-keys) section.

### Defaults are not encoded by default

Default values are not encoded by default in JSON. This behavior is motivated by the fact that in most real-life scenarios
such configuration reduces visual clutter, and saves the amount of data being serialized.

```kotlin
@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-09.kt).

It produces the following output, which does not have the `language` property because its value is equal to the default one.

```text
{"name":"kotlinx.serialization"}
```

<!--- TEST -->

See JSON's [Encoding defaults](json.md#encoding-defaults) section on how this behavior can be configured for JSON.
Additionally, this behavior can be controlled without taking format settings into account.
For that purposes, [EncodeDefault] annotation can be used:

```kotlin
@Serializable
data class Project(
    val name: String,
    @EncodeDefault val language: String = "Kotlin"
)
```

This annotation instructs the framework to always serialize property, regardless of its value or format settings.
It's also possible to tweak it into the opposite behavior using [EncodeDefault.Mode] parameter:

```kotlin

@Serializable
data class User(
    val name: String,
    @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List<Project> = emptyList()
)

fun main() {
    val userA = User("Alice", listOf(Project("kotlinx.serialization")))
    val userB = User("Bob")
    println(Json.encodeToString(userA))
    println(Json.encodeToString(userB))
}
```

> You can get the full code [here](../guide/example/example-classes-10.kt).

As you can see, `language` property is preserved and `projects` is omitted:

```text
{"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]}
{"name":"Bob"}
```

<!--- TEST -->

### Nullable properties

Nullable properties are natively supported by Kotlin Serialization.

```kotlin
@Serializable
class Project(val name: String, val renamedTo: String? = null)

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-11.kt).

This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded).

```text
{"name":"kotlinx.serialization"}
```

<!--- TEST -->

### Type safety is enforced

Kotlin Serialization strongly enforces the type safety of the Kotlin programming language.
In particular, let us try to decode a `null` value from a JSON object into a non-nullable Kotlin property `language`.

```kotlin
@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)
}
```

> You can get the full code [here](../guide/example/example-classes-12.kt).

Even though the `language` property has a default value, it is still an error to attempt to assign
the `null` value to it.

```text
Exception 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
Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value.
```

<!--- TEST LINES_START -->

> It might be desired, when decoding 3rd-party JSONs, to coerce `null` to a default value.
> The corresponding feature is explained in the [Coercing input values](json.md#coercing-input-values) section.

### Referenced objects

Serializable classes can reference other classes in their serializable properties.
The referenced classes must be also marked as `@Serializable`.

```kotlin
@Serializable
class Project(val name: String, val owner: User)

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner)
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-13.kt).

When encoded to JSON it results in a nested JSON object.

```text
{"name":"kotlinx.serialization","owner":{"name":"kotlin"}}
```

> References to non-serializable classes can be marked as [Transient properties](#transient-properties), or a
> custom serializer can be provided for them as shown in the [Serializers](serializers.md) chapter.

<!--- TEST -->

### No compression of repeated references

Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction
of arbitrary object graphs with repeated object references. For example, let us try to serialize an object
that references the same `owner` instance twice.

```kotlin
@Serializable
class Project(val name: String, val owner: User, val maintainer: User)

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner, owner)
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-14.kt).

We simply get the `owner` value encoded twice.

```text
{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}}
```

> Attempt to serialize a circular structure will result in stack overflow.
> You can use the [Transient properties](#transient-properties) to exclude some references from serialization.

<!--- TEST -->

### Generic classes

Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at
compile-time. For example, consider a generic serializable class `Box<T>`.

```kotlin
@Serializable
class Box<T>(val contents: T)
```

The `Box<T>` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`.

<!--- INCLUDE

@Serializable
data class Project(val name: String, val language: String)
-->

```kotlin
@Serializable
class Data(
    val a: Box<Int>,
    val b: Box<Project>
)

fun main() {
    val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-15.kt).

The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`.

```text
{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}}
```

<!--- TEST -->

If the actual generic type is not serializable a compile-time error will be produced.

### Serial field names

The names of the properties used in encoded representation, JSON in our examples, are the same as
their names in the source code by default. The name that is used for serialization is called a _serial name_, and
can be changed using the [`@SerialName`][SerialName] annotation. For example, we can have a `language` property in
the source with an abbreviated serial name.

```kotlin
@Serializable
class Project(val name: String, @SerialName("lang") val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}
```

> You can get the full code [here](../guide/example/example-classes-16.kt).

Now we see that an abbreviated name `lang` is used in the JSON output.

```text
{"name":"kotlinx.serialization","lang":"Kotlin"}
```

<!--- TEST -->

---

The next chapter covers [Builtin classes](builtin-classes.md).

<!-- stdlib references -->
[kotlin.jvm.Transient]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-transient/

<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->

[kotlinx.serialization.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html
[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
[kotlinx.serialization.decodeFromString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html
[Required]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/index.html
[Transient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html
[EncodeDefault]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/index.html
[EncodeDefault.Mode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/index.html
[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html

<!--- MODULE /kotlinx-serialization-json -->
<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
<!--- END -->