xref: /aosp_15_r20/external/kotlinx.serialization/docs/formats.md (revision 57b5a4a64c534cf7f27ac9427ceab07f3d8ed3d8)
1<!--- TEST_NAME FormatsTest -->
2
3# Alternative and custom formats (experimental)
4
5This is the sixth chapter of the [Kotlin Serialization Guide](serialization-guide.md).
6It goes beyond JSON, covering alternative and custom formats. Unlike JSON, which is
7stable, these are currently experimental features of Kotlin Serialization.
8
9**Table of contents**
10
11<!--- TOC -->
12
13* [CBOR (experimental)](#cbor-experimental)
14  * [Ignoring unknown keys](#ignoring-unknown-keys)
15  * [Byte arrays and CBOR data types](#byte-arrays-and-cbor-data-types)
16* [ProtoBuf (experimental)](#protobuf-experimental)
17  * [Field numbers](#field-numbers)
18  * [Integer types](#integer-types)
19  * [Lists as repeated fields](#lists-as-repeated-fields)
20  * [Packed fields](#packed-fields)
21  * [ProtoBuf schema generator (experimental)](#protobuf-schema-generator-experimental)
22* [Properties (experimental)](#properties-experimental)
23* [Custom formats (experimental)](#custom-formats-experimental)
24  * [Basic encoder](#basic-encoder)
25  * [Basic decoder](#basic-decoder)
26  * [Sequential decoding](#sequential-decoding)
27  * [Adding collection support](#adding-collection-support)
28  * [Adding null support](#adding-null-support)
29  * [Efficient binary format](#efficient-binary-format)
30  * [Format-specific types](#format-specific-types)
31
32<!--- END -->
33
34## CBOR (experimental)
35
36[CBOR][RFC 7049] is one of the standard compact binary
37encodings for JSON, so it supports a subset of [JSON features](json.md) and
38is generally very similar to JSON in use, but produces binary data.
39
40> CBOR support is (experimentally) available in a separate
41> `org.jetbrains.kotlinx:kotlinx-serialization-cbor:<version>` module.
42
43[Cbor] class has [Cbor.encodeToByteArray] and [Cbor.decodeFromByteArray] functions.
44Let us take the basic example from the [JSON encoding](basic-serialization.md#json-encoding),
45but encode it using CBOR.
46
47<!--- INCLUDE
48import kotlinx.serialization.*
49import kotlinx.serialization.cbor.*
50
51fun ByteArray.toAsciiHexString() = joinToString("") {
52    if (it in 32..127) it.toInt().toChar().toString() else
53        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
54}
55-->
56
57```kotlin
58@Serializable
59data class Project(val name: String, val language: String)
60
61fun main() {
62    val data = Project("kotlinx.serialization", "Kotlin")
63    val bytes = Cbor.encodeToByteArray(data)
64    println(bytes.toAsciiHexString())
65    val obj = Cbor.decodeFromByteArray<Project>(bytes)
66    println(obj)
67}
68```
69
70> You can get the full code [here](../guide/example/example-formats-01.kt).
71
72We print a filtered ASCII representation of the output, writing non-ASCII data in hex, so we see how
73all the original strings are directly represented in CBOR, but the format delimiters themselves are binary.
74
75```text
76{BF}dnameukotlinx.serializationhlanguagefKotlin{FF}
77Project(name=kotlinx.serialization, language=Kotlin)
78```
79
80<!--- TEST -->
81
82In [CBOR hex notation](http://cbor.me/), the output is equivalent to the following:
83```
84BF                                      # map(*)
85   64                                   # text(4)
86      6E616D65                          # "name"
87   75                                   # text(21)
88      6B6F746C696E782E73657269616C697A6174696F6E # "kotlinx.serialization"
89   68                                   # text(8)
90      6C616E6775616765                  # "language"
91   66                                   # text(6)
92      4B6F746C696E                      # "Kotlin"
93   FF                                   # primitive(*)
94```
95
96> Note, CBOR as a format, unlike JSON, supports maps with non-trivial keys
97> (see the [Allowing structured map keys](json.md#allowing-structured-map-keys) section for JSON workarounds),
98> and Kotlin maps are serialized as CBOR maps, but some parsers (like `jackson-dataformat-cbor`) don't support this.
99
100### Ignoring unknown keys
101
102CBOR format is often used to communicate with [IoT] devices where new properties could be added as a part of a device's
103API evolution. By default, unknown keys encountered during deserialization produce an error.
104This behavior can be configured with the [ignoreUnknownKeys][CborBuilder.ignoreUnknownKeys] property.
105
106<!--- INCLUDE
107import kotlinx.serialization.*
108import kotlinx.serialization.cbor.*
109-->
110
111```kotlin
112val format = Cbor { ignoreUnknownKeys = true }
113
114@Serializable
115data class Project(val name: String)
116
117fun main() {
118    val data = format.decodeFromHexString<Project>(
119        "bf646e616d65756b6f746c696e782e73657269616c697a6174696f6e686c616e6775616765664b6f746c696eff"
120    )
121    println(data)
122}
123```
124
125> You can get the full code [here](../guide/example/example-formats-02.kt).
126
127It decodes the object, despite the fact that `Project` is missing the `language` property.
128
129```text
130Project(name=kotlinx.serialization)
131```
132
133<!--- TEST -->
134
135In [CBOR hex notation](http://cbor.me/), the input is equivalent to the following:
136```
137BF                                      # map(*)
138   64                                   # text(4)
139      6E616D65                          # "name"
140   75                                   # text(21)
141      6B6F746C696E782E73657269616C697A6174696F6E # "kotlinx.serialization"
142   68                                   # text(8)
143      6C616E6775616765                  # "language"
144   66                                   # text(6)
145      4B6F746C696E                      # "Kotlin"
146   FF                                   # primitive(*)
147```
148
149### Byte arrays and CBOR data types
150
151Per the [RFC 7049 Major Types] section, CBOR supports the following data types:
152
153- Major type 0: an unsigned integer
154- Major type 1: a negative integer
155- **Major type 2: a byte string**
156- Major type 3: a text string
157- **Major type 4: an array of data items**
158- Major type 5: a map of pairs of data items
159- Major type 6: optional semantic tagging of other major types
160- Major type 7: floating-point numbers and simple data types that need no content, as well as the "break" stop code
161
162By default, Kotlin `ByteArray` instances are encoded as **major type 4**.
163When **major type 2** is desired, then the [`@ByteString`][ByteString] annotation can be used.
164
165<!--- INCLUDE
166import kotlinx.serialization.*
167import kotlinx.serialization.cbor.*
168
169fun ByteArray.toAsciiHexString() = joinToString("") {
170    if (it in 32..127) it.toInt().toChar().toString() else
171        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
172}
173-->
174
175```kotlin
176@Serializable
177data class Data(
178    @ByteString
179    val type2: ByteArray, // CBOR Major type 2
180    val type4: ByteArray  // CBOR Major type 4
181)
182
183fun main() {
184    val data = Data(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8))
185    val bytes = Cbor.encodeToByteArray(data)
186    println(bytes.toAsciiHexString())
187    val obj = Cbor.decodeFromByteArray<Data>(bytes)
188    println(obj)
189}
190```
191
192> You can get the full code [here](../guide/example/example-formats-03.kt).
193
194As we see, the CBOR byte that precedes the data is different for different types of encoding.
195
196```text
197{BF}etype2D{01}{02}{03}{04}etype4{9F}{05}{06}{07}{08}{FF}{FF}
198Data(type2=[1, 2, 3, 4], type4=[5, 6, 7, 8])
199```
200
201<!--- TEST -->
202
203In [CBOR hex notation](http://cbor.me/), the output is equivalent to the following:
204```
205BF               # map(*)
206   65            # text(5)
207      7479706532 # "type2"
208   44            # bytes(4)
209      01020304   # "\x01\x02\x03\x04"
210   65            # text(5)
211      7479706534 # "type4"
212   9F            # array(*)
213      05         # unsigned(5)
214      06         # unsigned(6)
215      07         # unsigned(7)
216      08         # unsigned(8)
217      FF         # primitive(*)
218   FF            # primitive(*)
219```
220
221## ProtoBuf (experimental)
222
223[Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally
224relies on a separate ".proto" file that defines the protocol schema. It is more compact than CBOR, because it
225assigns integer numbers to fields instead of names.
226
227> Protocol buffers support is (experimentally) available in a separate
228> `org.jetbrains.kotlinx:kotlinx-serialization-protobuf:<version>` module.
229
230Kotlin Serialization is using proto2 semantics, where all fields are explicitly required or optional.
231For a basic example we change our example to use the
232[ProtoBuf] class with [ProtoBuf.encodeToByteArray] and [ProtoBuf.decodeFromByteArray] functions.
233
234<!--- INCLUDE
235import kotlinx.serialization.*
236import kotlinx.serialization.protobuf.*
237
238fun ByteArray.toAsciiHexString() = joinToString("") {
239    if (it in 32..127) it.toInt().toChar().toString() else
240        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
241}
242-->
243
244```kotlin
245@Serializable
246data class Project(val name: String, val language: String)
247
248fun main() {
249    val data = Project("kotlinx.serialization", "Kotlin")
250    val bytes = ProtoBuf.encodeToByteArray(data)
251    println(bytes.toAsciiHexString())
252    val obj = ProtoBuf.decodeFromByteArray<Project>(bytes)
253    println(obj)
254}
255```
256
257> You can get the full code [here](../guide/example/example-formats-04.kt).
258
259```text
260{0A}{15}kotlinx.serialization{12}{06}Kotlin
261Project(name=kotlinx.serialization, language=Kotlin)
262```
263
264<!--- TEST -->
265
266In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following:
267```
268Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization"
269Field #2: 12 String Length = 6, Hex = 06, UTF8 = "Kotlin"
270```
271
272### Field numbers
273
274By default, field numbers in the Kotlin Serialization [ProtoBuf] implementation are automatically assigned,
275which does not provide the ability to define a stable data schema that evolves over time. That is normally achieved by
276writing a separate ".proto" file. However, with Kotlin Serialization we can get this ability without a separate
277schema file, instead using the [ProtoNumber] annotation.
278
279<!--- INCLUDE
280import kotlinx.serialization.*
281import kotlinx.serialization.protobuf.*
282
283fun ByteArray.toAsciiHexString() = joinToString("") {
284    if (it in 32..127) it.toInt().toChar().toString() else
285        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
286}
287-->
288
289```kotlin
290@Serializable
291data class Project(
292    @ProtoNumber(1)
293    val name: String,
294    @ProtoNumber(3)
295    val language: String
296)
297
298fun main() {
299    val data = Project("kotlinx.serialization", "Kotlin")
300    val bytes = ProtoBuf.encodeToByteArray(data)
301    println(bytes.toAsciiHexString())
302    val obj = ProtoBuf.decodeFromByteArray<Project>(bytes)
303    println(obj)
304}
305```
306
307> You can get the full code [here](../guide/example/example-formats-05.kt).
308
309We see in the output that the number for the first property `name` did not change (as it is numbered from one by default),
310but it did change for the `language` property.
311
312```text
313{0A}{15}kotlinx.serialization{1A}{06}Kotlin
314Project(name=kotlinx.serialization, language=Kotlin)
315```
316
317<!--- TEST -->
318
319In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following:
320```
321Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization" (total 21 chars)
322Field #3: 1A String Length = 6, Hex = 06, UTF8 = "Kotlin"
323```
324
325### Integer types
326
327Protocol buffers support various integer encodings optimized for different ranges of integers.
328They are specified using the [ProtoType] annotation and the [ProtoIntegerType] enum.
329The following example shows all three supported options.
330
331<!--- INCLUDE
332import kotlinx.serialization.*
333import kotlinx.serialization.protobuf.*
334
335fun ByteArray.toAsciiHexString() = joinToString("") {
336    if (it in 32..127) it.toInt().toChar().toString() else
337        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
338}
339-->
340
341```kotlin
342@Serializable
343class Data(
344    @ProtoType(ProtoIntegerType.DEFAULT)
345    val a: Int,
346    @ProtoType(ProtoIntegerType.SIGNED)
347    val b: Int,
348    @ProtoType(ProtoIntegerType.FIXED)
349    val c: Int
350)
351
352fun main() {
353    val data = Data(1, -2, 3)
354    println(ProtoBuf.encodeToByteArray(data).toAsciiHexString())
355}
356```
357
358> You can get the full code [here](../guide/example/example-formats-06.kt).
359
360* The [default][ProtoIntegerType.DEFAULT] is a varint encoding (`intXX`) that is optimized for
361  small non-negative numbers. The value of `1` is encoded in one byte `01`.
362* The [signed][ProtoIntegerType.SIGNED] is a signed ZigZag encoding (`sintXX`) that is optimized for
363  small signed integers. The value of `-2` is encoded in one byte `03`.
364* The [fixed][ProtoIntegerType.FIXED] encoding (`fixedXX`) always uses a fixed number of bytes.
365  The value of `3` is encoded as four bytes `03 00 00 00`.
366
367> `uintXX` and `sfixedXX` protocol buffer types are not supported.
368
369```text
370{08}{01}{10}{03}{1D}{03}{00}{00}{00}
371```
372
373<!--- TEST -->
374
375In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode) the output is equivalent to the following:
376```
377Field #1: 08 Varint Value = 1, Hex = 01
378Field #2: 10 Varint Value = 3, Hex = 03
379Field #3: 1D Fixed32 Value = 3, Hex = 03-00-00-00
380```
381
382### Lists as repeated fields
383
384By default, kotlin lists and other collections are representend as repeated fields.
385In the protocol buffers when the list is empty there are no elements in the
386stream with the corresponding number. For Kotlin Serialization you must explicitly specify a default of `emptyList()`
387for any property of a collection or map type. Otherwise you will not be able deserialize an empty
388list, which is indistinguishable in protocol buffers from a missing field.
389
390<!--- INCLUDE
391import kotlinx.serialization.*
392import kotlinx.serialization.protobuf.*
393
394fun ByteArray.toAsciiHexString() = joinToString("") {
395    if (it in 32..127) it.toInt().toChar().toString() else
396        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
397}
398-->
399
400```kotlin
401@Serializable
402data class Data(
403    val a: List<Int> = emptyList(),
404    val b: List<Int> = emptyList()
405)
406
407fun main() {
408    val data = Data(listOf(1, 2, 3), listOf())
409    val bytes = ProtoBuf.encodeToByteArray(data)
410    println(bytes.toAsciiHexString())
411    println(ProtoBuf.decodeFromByteArray<Data>(bytes))
412}
413```
414
415> You can get the full code [here](../guide/example/example-formats-07.kt).
416
417```text
418{08}{01}{08}{02}{08}{03}
419Data(a=[1, 2, 3], b=[])
420```
421
422<!--- TEST -->
423
424In [ProtoBuf diagnostic mode](https://protogen.marcgravell.com/decode) the output is equivalent to the following:
425```
426Field #1: 08 Varint Value = 1, Hex = 01
427Field #1: 08 Varint Value = 2, Hex = 02
428Field #1: 08 Varint Value = 3, Hex = 03
429```
430
431### Packed fields
432Collection types (not maps) can be **written** as packed fields when annotated with the `@ProtoPacked` annotation.
433Per the standard packed fields can only be used on primitive numeric types. The annotation is ignored on other types.
434
435Per the [format description](https://developers.google.com/protocol-buffers/docs/encoding#packed) the parser ignores
436the annotation, but rather reads list in either packed or repeated format.
437
438### ProtoBuf schema generator (experimental)
439
440As mentioned above, when working with protocol buffers you usually use a ".proto" file and a code generator for your
441language.  This includes the code to serialize your message to an output stream and deserialize it from an input stream.
442When using Kotlin Serialization this step is not necessary because your `@Serializable` Kotlin data types are used as the
443source for the schema.
444
445This is very convenient for Kotlin-to-Kotlin communication, but makes interoperability between languages complicated.
446Fortunately, you can use the ProtoBuf schema generator to output the ".proto" representation of your messages.  You can
447keep your Kotlin classes as a source of truth and use traditional protoc compilers for other languages at the same time.
448
449As an example, we can display the following data class's ".proto" schema as follows.
450
451<!--- INCLUDE
452import kotlinx.serialization.*
453import kotlinx.serialization.protobuf.*
454import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
455-->
456
457```kotlin
458@Serializable
459data class SampleData(
460    val amount: Long,
461    val description: String?,
462    val department: String = "QA"
463)
464fun main() {
465  val descriptors = listOf(SampleData.serializer().descriptor)
466  val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors)
467  println(schemas)
468}
469```
470> You can get the full code [here](../guide/example/example-formats-08.kt).
471
472Which would output as follows.
473
474```text
475syntax = "proto2";
476
477
478// serial name 'example.exampleFormats08.SampleData'
479message SampleData {
480  required int64 amount = 1;
481  optional string description = 2;
482  // WARNING: a default value decoded when value is missing
483  optional string department = 3;
484}
485
486```
487
488<!--- TEST -->
489
490Note that since default values are not represented in ".proto" files, a warning is generated when one appears in the schema.
491
492See the documentation for [ProtoBufSchemaGenerator] for more information.
493
494## Properties (experimental)
495
496Kotlin Serialization can serialize a class into a flat map with `String` keys via
497the [Properties][kotlinx.serialization.properties.Properties] format implementation.
498
499> Properties support is (experimentally) available in a separate
500> `org.jetbrains.kotlinx:kotlinx-serialization-properties:<version>` module.
501
502<!--- INCLUDE
503import kotlinx.serialization.*
504import kotlinx.serialization.properties.Properties // todo: remove when no longer needed
505import kotlinx.serialization.properties.*
506-->
507
508```kotlin
509@Serializable
510class Project(val name: String, val owner: User)
511
512@Serializable
513class User(val name: String)
514
515fun main() {
516    val data = Project("kotlinx.serialization",  User("kotlin"))
517    val map = Properties.encodeToMap(data)
518    map.forEach { (k, v) -> println("$k = $v") }
519}
520```
521
522> You can get the full code [here](../guide/example/example-formats-09.kt).
523
524The resulting map has dot-separated keys representing keys of the nested objects.
525
526```text
527name = kotlinx.serialization
528owner.name = kotlin
529```
530
531<!--- TEST -->
532
533## Custom formats (experimental)
534
535A custom format for Kotlin Serialization must provide an implementation for the [Encoder] and [Decoder] interfaces that
536we saw used in the [Serializers](serializers.md) chapter.
537These are pretty large interfaces. For convenience
538the [AbstractEncoder] and [AbstractDecoder] skeleton implementations are provided to simplify the task.
539In [AbstractEncoder] most of the `encodeXxx` methods have a default implementation that
540delegates to [`encodeValue(value: Any)`][AbstractEncoder.encodeValue] &mdash; the only method that must be
541implemented to get a basic working format.
542
543### Basic encoder
544
545Let us start with a trivial format implementation that encodes the data into a single list of primitive
546constituent objects in the order they were written in the source code. To start, we implement a simple [Encoder] by
547overriding `encodeValue` in [AbstractEncoder].
548
549<!--- INCLUDE
550import kotlinx.serialization.*
551import kotlinx.serialization.descriptors.*
552import kotlinx.serialization.encoding.*
553import kotlinx.serialization.modules.*
554-->
555
556```kotlin
557class ListEncoder : AbstractEncoder() {
558    val list = mutableListOf<Any>()
559
560    override val serializersModule: SerializersModule = EmptySerializersModule()
561
562    override fun encodeValue(value: Any) {
563        list.add(value)
564    }
565}
566```
567
568Now we write a convenience top-level function that creates an encoder that encodes an object
569and returns a list.
570
571```kotlin
572fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
573    val encoder = ListEncoder()
574    encoder.encodeSerializableValue(serializer, value)
575    return encoder.list
576}
577```
578
579For even more convenience, to avoid the need to explicitly pass a serializer, we write an `inline` overload of
580the `encodeToList` function with a `reified` type parameter using the [serializer] function to retrieve
581the appropriate [KSerializer] instance for the actual type.
582
583```kotlin
584inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
585```
586
587Now we can test it.
588
589```kotlin
590@Serializable
591data class Project(val name: String, val owner: User, val votes: Int)
592
593@Serializable
594data class User(val name: String)
595
596fun main() {
597    val data = Project("kotlinx.serialization",  User("kotlin"), 9000)
598    println(encodeToList(data))
599}
600```
601
602> You can get the full code [here](../guide/example/example-formats-10.kt).
603
604As a result, we got all the primitive values in our object graph visited and put into a list
605in _serial_ order.
606
607```text
608[kotlinx.serialization, kotlin, 9000]
609```
610
611<!--- TEST -->
612
613> By itself, that's a useful feature if we need compute some kind of hashcode or digest for all the data
614> that is contained in a serializable object tree.
615
616### Basic decoder
617
618<!--- INCLUDE
619import kotlinx.serialization.*
620import kotlinx.serialization.descriptors.*
621import kotlinx.serialization.encoding.*
622import kotlinx.serialization.modules.*
623
624class ListEncoder : AbstractEncoder() {
625    val list = mutableListOf<Any>()
626
627    override val serializersModule: SerializersModule = EmptySerializersModule()
628
629    override fun encodeValue(value: Any) {
630        list.add(value)
631    }
632}
633
634fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
635    val encoder = ListEncoder()
636    encoder.encodeSerializableValue(serializer, value)
637    return encoder.list
638}
639
640inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
641-->
642
643A decoder needs to implement more substance.
644
645* [decodeValue][AbstractDecoder.decodeValue] &mdash; returns the next value from the list.
646* [decodeElementIndex][CompositeDecoder.decodeElementIndex] &mdash; returns the next index of a deserialized value.
647  In this primitive format deserialization always happens in order, so we keep track of the index
648  in the `elementIndex` variable. See
649  the [Hand-written composite serializer](serializers.md#hand-written-composite-serializer) section
650  on how it ends up being used.
651* [beginStructure][Decoder.beginStructure] &mdash; returns a new instance of the `ListDecoder`, so that
652  each structure that is being recursively decoded keeps track of its own `elementIndex` state separately.
653
654```kotlin
655class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
656    private var elementIndex = 0
657
658    override val serializersModule: SerializersModule = EmptySerializersModule()
659
660    override fun decodeValue(): Any = list.removeFirst()
661
662    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
663        if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE
664        return elementIndex++
665    }
666
667    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
668        ListDecoder(list)
669}
670```
671
672A couple of convenience functions for decoding.
673
674```kotlin
675fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
676    val decoder = ListDecoder(ArrayDeque(list))
677    return decoder.decodeSerializableValue(deserializer)
678}
679
680inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
681```
682
683That is enough to start encoding and decoding basic serializable classes.
684
685<!--- INCLUDE
686
687@Serializable
688data class Project(val name: String, val owner: User, val votes: Int)
689
690@Serializable
691data class User(val name: String)
692-->
693
694```kotlin
695fun main() {
696    val data = Project("kotlinx.serialization",  User("kotlin"), 9000)
697    val list = encodeToList(data)
698    println(list)
699    val obj = decodeFromList<Project>(list)
700    println(obj)
701}
702```
703
704> You can get the full code [here](../guide/example/example-formats-11.kt).
705
706Now we can convert a list of primitives back to an object tree.
707
708```text
709[kotlinx.serialization, kotlin, 9000]
710Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000)
711```
712
713<!--- TEST -->
714
715### Sequential decoding
716
717The decoder we have implemented keeps track of the `elementIndex` in its state and implements
718`decodeElementIndex`. This means that it is going to work with an arbitrary serializer, even the
719simple one we wrote in
720the [Hand-written composite serializer](serializers.md#hand-written-composite-serializer) section.
721However, this format always stores elements in order, so this bookkeeping is not needed and
722undermines decoding performance. All auto-generated serializers on the JVM support
723the [Sequential decoding protocol (experimental)](serializers.md#sequential-decoding-protocol-experimental), and the decoder can indicate
724its support by returning `true` from the [CompositeDecoder.decodeSequentially] function.
725
726<!--- INCLUDE
727import kotlinx.serialization.*
728import kotlinx.serialization.descriptors.*
729import kotlinx.serialization.encoding.*
730import kotlinx.serialization.modules.*
731
732class ListEncoder : AbstractEncoder() {
733    val list = mutableListOf<Any>()
734
735    override val serializersModule: SerializersModule = EmptySerializersModule()
736
737    override fun encodeValue(value: Any) {
738        list.add(value)
739    }
740}
741
742fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
743    val encoder = ListEncoder()
744    encoder.encodeSerializableValue(serializer, value)
745    return encoder.list
746}
747
748inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
749-->
750
751```kotlin
752class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
753    private var elementIndex = 0
754
755    override val serializersModule: SerializersModule = EmptySerializersModule()
756
757    override fun decodeValue(): Any = list.removeFirst()
758
759    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
760        if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE
761        return elementIndex++
762    }
763
764    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
765        ListDecoder(list)
766
767    override fun decodeSequentially(): Boolean = true
768}
769```
770
771<!--- INCLUDE
772
773fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
774    val decoder = ListDecoder(ArrayDeque(list))
775    return decoder.decodeSerializableValue(deserializer)
776}
777
778inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
779
780@Serializable
781data class Project(val name: String, val owner: User, val votes: Int)
782
783@Serializable
784data class User(val name: String)
785
786fun main() {
787    val data = Project("kotlinx.serialization",  User("kotlin"), 9000)
788    val list = encodeToList(data)
789    println(list)
790    val obj = decodeFromList<Project>(list)
791    println(obj)
792}
793-->
794
795> You can get the full code [here](../guide/example/example-formats-12.kt).
796
797<!--- TEST
798[kotlinx.serialization, kotlin, 9000]
799Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000)
800-->
801
802### Adding collection support
803
804This basic format, so far, cannot properly represent collections. In encodes them, but it does not keep
805track of how many elements there are in the collection or where it ends, so it cannot properly decode them.
806First, let us add proper support for collections to the encoder by implementing the
807[Encoder.beginCollection] function. The `beginCollection` function takes a collection size as a parameter,
808so we encode it to add it to the result.
809Our encoder implementation does not keep any state, so it just returns `this` from the `beginCollection` function.
810
811<!--- INCLUDE
812import kotlinx.serialization.*
813import kotlinx.serialization.descriptors.*
814import kotlinx.serialization.encoding.*
815import kotlinx.serialization.modules.*
816-->
817
818```kotlin
819class ListEncoder : AbstractEncoder() {
820    val list = mutableListOf<Any>()
821
822    override val serializersModule: SerializersModule = EmptySerializersModule()
823
824    override fun encodeValue(value: Any) {
825        list.add(value)
826    }
827
828    override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder {
829        encodeInt(collectionSize)
830        return this
831    }
832}
833```
834
835<!--- INCLUDE
836
837fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
838    val encoder = ListEncoder()
839    encoder.encodeSerializableValue(serializer, value)
840    return encoder.list
841}
842
843inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
844-->
845
846The decoder, for our case, needs to only implement the [CompositeDecoder.decodeCollectionSize] function
847in addition to the previous code.
848
849> The formats that store collection size in advance have to return `true` from `decodeSequentially`.
850
851```kotlin
852class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
853    private var elementIndex = 0
854
855    override val serializersModule: SerializersModule = EmptySerializersModule()
856
857    override fun decodeValue(): Any = list.removeFirst()
858
859    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
860        if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE
861        return elementIndex++
862    }
863
864    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
865        ListDecoder(list, descriptor.elementsCount)
866
867    override fun decodeSequentially(): Boolean = true
868
869    override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
870        decodeInt().also { elementsCount = it }
871}
872```
873
874<!--- INCLUDE
875
876fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
877    val decoder = ListDecoder(ArrayDeque(list))
878    return decoder.decodeSerializableValue(deserializer)
879}
880
881inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
882-->
883
884That is all that is needed to support collections and maps.
885
886```kotlin
887@Serializable
888data class Project(val name: String, val owners: List<User>, val votes: Int)
889
890@Serializable
891data class User(val name: String)
892
893fun main() {
894    val data = Project("kotlinx.serialization",  listOf(User("kotlin"), User("jetbrains")), 9000)
895    val list = encodeToList(data)
896    println(list)
897    val obj = decodeFromList<Project>(list)
898    println(obj)
899}
900```
901
902> You can get the full code [here](../guide/example/example-formats-13.kt).
903
904We see the size of the list added to the result, letting the decoder know where to stop.
905
906```text
907[kotlinx.serialization, 2, kotlin, jetbrains, 9000]
908Project(name=kotlinx.serialization, owners=[User(name=kotlin), User(name=jetbrains)], votes=9000)
909```
910
911<!--- TEST -->
912
913### Adding null support
914
915Our trivial format does not support `null` values so far. For nullable types we need to add some kind
916of "null indicator", telling whether the upcoming value is null or not.
917
918<!--- INCLUDE
919import kotlinx.serialization.*
920import kotlinx.serialization.descriptors.*
921import kotlinx.serialization.encoding.*
922import kotlinx.serialization.modules.*
923
924class ListEncoder : AbstractEncoder() {
925    val list = mutableListOf<Any>()
926
927    override val serializersModule: SerializersModule = EmptySerializersModule()
928
929    override fun encodeValue(value: Any) {
930        list.add(value)
931    }
932
933    override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder {
934        encodeInt(collectionSize)
935        return this
936    }
937-->
938
939In the encoder implementation we override [Encoder.encodeNull] and [Encoder.encodeNotNullMark].
940
941```kotlin
942    override fun encodeNull() = encodeValue("NULL")
943    override fun encodeNotNullMark() = encodeValue("!!")
944```
945
946<!--- INCLUDE
947}
948
949fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
950    val encoder = ListEncoder()
951    encoder.encodeSerializableValue(serializer, value)
952    return encoder.list
953}
954
955inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
956
957class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
958    private var elementIndex = 0
959
960    override val serializersModule: SerializersModule = EmptySerializersModule()
961
962    override fun decodeValue(): Any = list.removeFirst()
963
964    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
965        if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE
966        return elementIndex++
967    }
968
969    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
970        ListDecoder(list, descriptor.elementsCount)
971
972    override fun decodeSequentially(): Boolean = true
973
974    override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
975        decodeInt().also { elementsCount = it }
976-->
977
978In the decoder implementation we override [Decoder.decodeNotNullMark].
979
980```kotlin
981    override fun decodeNotNullMark(): Boolean = decodeString() != "NULL"
982```
983
984<!--- INCLUDE
985}
986
987fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
988    val decoder = ListDecoder(ArrayDeque(list))
989    return decoder.decodeSerializableValue(deserializer)
990}
991
992inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
993-->
994
995Let us test nullable properties both with not-null and null values.
996
997```kotlin
998@Serializable
999data class Project(val name: String, val owner: User?, val votes: Int?)
1000
1001@Serializable
1002data class User(val name: String)
1003
1004fun main() {
1005    val data = Project("kotlinx.serialization",  User("kotlin") , null)
1006    val list = encodeToList(data)
1007    println(list)
1008    val obj = decodeFromList<Project>(list)
1009    println(obj)
1010}
1011
1012```
1013
1014> You can get the full code [here](../guide/example/example-formats-14.kt).
1015
1016In the output we see how not-null`!!` and `NULL` marks are used.
1017
1018```text
1019[kotlinx.serialization, !!, kotlin, NULL]
1020Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=null)
1021```
1022
1023<!--- TEST -->
1024
1025### Efficient binary format
1026
1027Now we are ready for an example of an efficient binary format. We are going to write data to the
1028[java.io.DataOutput] implementation. Instead of `encodeValue` we must override the individual
1029`encodeXxx` functions for each of ten [primitives](builtin-classes.md#primitives) in the encoder.
1030
1031<!--- INCLUDE
1032import kotlinx.serialization.*
1033import kotlinx.serialization.Serializable
1034import kotlinx.serialization.descriptors.*
1035import kotlinx.serialization.encoding.*
1036import kotlinx.serialization.modules.*
1037import java.io.*
1038-->
1039
1040```kotlin
1041class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
1042    override val serializersModule: SerializersModule = EmptySerializersModule()
1043    override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
1044    override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
1045    override fun encodeShort(value: Short) = output.writeShort(value.toInt())
1046    override fun encodeInt(value: Int) = output.writeInt(value)
1047    override fun encodeLong(value: Long) = output.writeLong(value)
1048    override fun encodeFloat(value: Float) = output.writeFloat(value)
1049    override fun encodeDouble(value: Double) = output.writeDouble(value)
1050    override fun encodeChar(value: Char) = output.writeChar(value.code)
1051    override fun encodeString(value: String) = output.writeUTF(value)
1052    override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index)
1053
1054    override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder {
1055        encodeInt(collectionSize)
1056        return this
1057    }
1058
1059    override fun encodeNull() = encodeBoolean(false)
1060    override fun encodeNotNullMark() = encodeBoolean(true)
1061}
1062```
1063
1064<!--- INCLUDE
1065
1066fun <T> encodeTo(output: DataOutput, serializer: SerializationStrategy<T>, value: T) {
1067    val encoder = DataOutputEncoder(output)
1068    encoder.encodeSerializableValue(serializer, value)
1069}
1070
1071inline fun <reified T> encodeTo(output: DataOutput, value: T) = encodeTo(output, serializer(), value)
1072-->
1073
1074The decoder implementation mirrors encoder's implementation overriding all the primitive `decodeXxx` functions.
1075
1076```kotlin
1077class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() {
1078    private var elementIndex = 0
1079    override val serializersModule: SerializersModule = EmptySerializersModule()
1080    override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
1081    override fun decodeByte(): Byte = input.readByte()
1082    override fun decodeShort(): Short = input.readShort()
1083    override fun decodeInt(): Int = input.readInt()
1084    override fun decodeLong(): Long = input.readLong()
1085    override fun decodeFloat(): Float = input.readFloat()
1086    override fun decodeDouble(): Double = input.readDouble()
1087    override fun decodeChar(): Char = input.readChar()
1088    override fun decodeString(): String = input.readUTF()
1089    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt()
1090
1091    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
1092        if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE
1093        return elementIndex++
1094    }
1095
1096    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
1097        DataInputDecoder(input, descriptor.elementsCount)
1098
1099    override fun decodeSequentially(): Boolean = true
1100
1101    override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
1102        decodeInt().also { elementsCount = it }
1103
1104    override fun decodeNotNullMark(): Boolean = decodeBoolean()
1105}
1106```
1107
1108<!--- INCLUDE
1109
1110fun <T> decodeFrom(input: DataInput, deserializer: DeserializationStrategy<T>): T {
1111    val decoder = DataInputDecoder(input)
1112    return decoder.decodeSerializableValue(deserializer)
1113}
1114
1115inline fun <reified T> decodeFrom(input: DataInput): T = decodeFrom(input, serializer())
1116
1117fun ByteArray.toAsciiHexString() = joinToString("") {
1118    if (it in 32..127) it.toInt().toChar().toString() else
1119        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
1120}
1121-->
1122
1123We can now serialize and deserialize arbitrary data. For example, the same classes as were
1124used in the [CBOR (experimental)](#cbor-experimental) and [ProtoBuf (experimental)](#protobuf-experimental) sections.
1125
1126```kotlin
1127@Serializable
1128data class Project(val name: String, val language: String)
1129
1130fun main() {
1131    val data = Project("kotlinx.serialization", "Kotlin")
1132    val output = ByteArrayOutputStream()
1133    encodeTo(DataOutputStream(output), data)
1134    val bytes = output.toByteArray()
1135    println(bytes.toAsciiHexString())
1136    val input = ByteArrayInputStream(bytes)
1137    val obj = decodeFrom<Project>(DataInputStream(input))
1138    println(obj)
1139}
1140```
1141
1142> You can get the full code [here](../guide/example/example-formats-15.kt).
1143
1144As we can see, the result is a dense binary format that only contains the data that is being serialized.
1145It can be easily tweaked for any kind of domain-specific compact encoding.
1146
1147```text
1148{00}{15}kotlinx.serialization{00}{06}Kotlin
1149Project(name=kotlinx.serialization, language=Kotlin)
1150```
1151
1152<!--- TEST -->
1153
1154### Format-specific types
1155
1156A format implementation might provide special support for data types that are not among the list of primitive
1157types in Kotlin Serialization, and do not have a corresponding `encodeXxx`/`decodeXxx` function.
1158In the encoder this is achieved by overriding the
1159[`encodeSerializableValue(serializer, value)`][Encoder.encodeSerializableValue] function.
1160
1161In our `DataOutput` format example we might want to provide a specialized efficient data path for serializing an array
1162of bytes since [DataOutput][java.io.DataOutput] has a special method for this purpose.
1163
1164Detection of the type is performed by looking at the `serializer.descriptor`, not by checking the type of the `value`
1165being serialized, so we fetch the builtin [KSerializer] instance for `ByteArray` type.
1166
1167> This an important difference. This way our format implementation properly supports
1168> [Custom serializers](serializers.md#custom-serializers) that a user might specify for a type that just happens
1169> to be internally represented as a byte array, but need a different serial representation.
1170
1171<!--- INCLUDE
1172import kotlinx.serialization.*
1173import kotlinx.serialization.Serializable
1174import kotlinx.serialization.descriptors.*
1175import kotlinx.serialization.modules.*
1176import kotlinx.serialization.encoding.*
1177import java.io.*
1178-->
1179
1180```kotlin
1181private val byteArraySerializer = serializer<ByteArray>()
1182```
1183
1184> Specifically for byte arrays, we could have also used the builtin
1185> [ByteArraySerializer][kotlinx.serialization.builtins.ByteArraySerializer()] function.
1186
1187We add the corresponding code to the [Encoder] implementation of our
1188[Efficient binary format](#efficient-binary-format). To make our `ByteArray` encoding even more efficient,
1189we add a trivial implementation of `encodeCompactSize` function that uses only one byte to represent
1190a size of up to 254 bytes.
1191
1192<!--- INCLUDE
1193class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
1194    override val serializersModule: SerializersModule = EmptySerializersModule()
1195    override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
1196    override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
1197    override fun encodeShort(value: Short) = output.writeShort(value.toInt())
1198    override fun encodeInt(value: Int) = output.writeInt(value)
1199    override fun encodeLong(value: Long) = output.writeLong(value)
1200    override fun encodeFloat(value: Float) = output.writeFloat(value)
1201    override fun encodeDouble(value: Double) = output.writeDouble(value)
1202    override fun encodeChar(value: Char) = output.writeChar(value.code)
1203    override fun encodeString(value: String) = output.writeUTF(value)
1204    override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index)
1205
1206    override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder {
1207        encodeInt(collectionSize)
1208        return this
1209    }
1210
1211    override fun encodeNull() = encodeBoolean(false)
1212    override fun encodeNotNullMark() = encodeBoolean(true)
1213-->
1214
1215```kotlin
1216    override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
1217        if (serializer.descriptor == byteArraySerializer.descriptor)
1218            encodeByteArray(value as ByteArray)
1219        else
1220            super.encodeSerializableValue(serializer, value)
1221    }
1222
1223    private fun encodeByteArray(bytes: ByteArray) {
1224        encodeCompactSize(bytes.size)
1225        output.write(bytes)
1226    }
1227
1228    private fun encodeCompactSize(value: Int) {
1229        if (value < 0xff) {
1230            output.writeByte(value)
1231        } else {
1232            output.writeByte(0xff)
1233            output.writeInt(value)
1234        }
1235    }
1236```
1237
1238<!--- INCLUDE
1239}
1240
1241fun <T> encodeTo(output: DataOutput, serializer: SerializationStrategy<T>, value: T) {
1242    val encoder = DataOutputEncoder(output)
1243    encoder.encodeSerializableValue(serializer, value)
1244}
1245
1246inline fun <reified T> encodeTo(output: DataOutput, value: T) = encodeTo(output, serializer(), value)
1247
1248class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() {
1249    private var elementIndex = 0
1250    override val serializersModule: SerializersModule = EmptySerializersModule()
1251    override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
1252    override fun decodeByte(): Byte = input.readByte()
1253    override fun decodeShort(): Short = input.readShort()
1254    override fun decodeInt(): Int = input.readInt()
1255    override fun decodeLong(): Long = input.readLong()
1256    override fun decodeFloat(): Float = input.readFloat()
1257    override fun decodeDouble(): Double = input.readDouble()
1258    override fun decodeChar(): Char = input.readChar()
1259    override fun decodeString(): String = input.readUTF()
1260    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt()
1261
1262    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
1263        if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE
1264        return elementIndex++
1265    }
1266
1267    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
1268        DataInputDecoder(input, descriptor.elementsCount)
1269
1270    override fun decodeSequentially(): Boolean = true
1271
1272    override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
1273        decodeInt().also { elementsCount = it }
1274
1275    override fun decodeNotNullMark(): Boolean = decodeBoolean()
1276-->
1277
1278A similar code is added to the [Decoder] implementation. Here we override
1279the [decodeSerializableValue][Decoder.decodeSerializableValue] function.
1280
1281```kotlin
1282    @Suppress("UNCHECKED_CAST")
1283    override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T =
1284        if (deserializer.descriptor == byteArraySerializer.descriptor)
1285            decodeByteArray() as T
1286        else
1287            super.decodeSerializableValue(deserializer, previousValue)
1288
1289    private fun decodeByteArray(): ByteArray {
1290        val bytes = ByteArray(decodeCompactSize())
1291        input.readFully(bytes)
1292        return bytes
1293    }
1294
1295    private fun decodeCompactSize(): Int {
1296        val byte = input.readByte().toInt() and 0xff
1297        if (byte < 0xff) return byte
1298        return input.readInt()
1299    }
1300```
1301
1302<!--- INCLUDE
1303}
1304
1305fun <T> decodeFrom(input: DataInput, deserializer: DeserializationStrategy<T>): T {
1306    val decoder = DataInputDecoder(input)
1307    return decoder.decodeSerializableValue(deserializer)
1308}
1309
1310inline fun <reified T> decodeFrom(input: DataInput): T = decodeFrom(input, serializer())
1311
1312fun ByteArray.toAsciiHexString() = joinToString("") {
1313    if (it in 32..127) it.toInt().toChar().toString() else
1314        "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}"
1315}
1316-->
1317
1318Now everything is ready to perform serialization of some byte arrays.
1319
1320```kotlin
1321@Serializable
1322data class Project(val name: String, val attachment: ByteArray)
1323
1324fun main() {
1325    val data = Project("kotlinx.serialization", byteArrayOf(0x0A, 0x0B, 0x0C, 0x0D))
1326    val output = ByteArrayOutputStream()
1327    encodeTo(DataOutputStream(output), data)
1328    val bytes = output.toByteArray()
1329    println(bytes.toAsciiHexString())
1330    val input = ByteArrayInputStream(bytes)
1331    val obj = decodeFrom<Project>(DataInputStream(input))
1332    println(obj)
1333}
1334```
1335
1336> You can get the full code [here](../guide/example/example-formats-16.kt).
1337
1338As we can see, our custom byte array format is being used, with the compact encoding of its size in one byte.
1339
1340```text
1341{00}{15}kotlinx.serialization{04}{0A}{0B}{0C}{0D}
1342Project(name=kotlinx.serialization, attachment=[10, 11, 12, 13])
1343```
1344
1345<!--- TEST -->
1346
1347---
1348
1349This chapter concludes [Kotlin Serialization Guide](serialization-guide.md).
1350
1351
1352<!-- references -->
1353[RFC 7049]: https://tools.ietf.org/html/rfc7049
1354[IoT]: https://en.wikipedia.org/wiki/Internet_of_things
1355[RFC 7049 Major Types]: https://tools.ietf.org/html/rfc7049#section-2.1
1356
1357<!-- Java references -->
1358[java.io.DataOutput]: https://docs.oracle.com/javase/8/docs/api/java/io/DataOutput.html
1359
1360<!--- MODULE /kotlinx-serialization-core -->
1361<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
1362
1363[serializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/serializer.html
1364[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
1365
1366<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.builtins -->
1367
1368[kotlinx.serialization.builtins.ByteArraySerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-byte-array-serializer.html
1369
1370<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding -->
1371
1372[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
1373[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
1374[AbstractEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/index.html
1375[AbstractDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/index.html
1376[AbstractEncoder.encodeValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/encode-value.html
1377[AbstractDecoder.decodeValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/decode-value.html
1378[CompositeDecoder.decodeElementIndex]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html
1379[Decoder.beginStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/begin-structure.html
1380[CompositeDecoder.decodeSequentially]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html
1381[Encoder.beginCollection]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/begin-collection.html
1382[CompositeDecoder.decodeCollectionSize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-collection-size.html
1383[Encoder.encodeNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-null.html
1384[Encoder.encodeNotNullMark]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-not-null-mark.html
1385[Decoder.decodeNotNullMark]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-not-null-mark.html
1386[Encoder.encodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html
1387[Decoder.decodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html
1388
1389<!--- MODULE /kotlinx-serialization-properties -->
1390<!--- INDEX kotlinx-serialization-properties/kotlinx.serialization.properties -->
1391
1392[kotlinx.serialization.properties.Properties]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-properties/kotlinx.serialization.properties/-properties/index.html
1393
1394<!--- MODULE /kotlinx-serialization-protobuf -->
1395<!--- INDEX kotlinx-serialization-protobuf/kotlinx.serialization.protobuf -->
1396
1397[ProtoBuf]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/index.html
1398[ProtoBuf.encodeToByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/encode-to-byte-array.html
1399[ProtoBuf.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/decode-from-byte-array.html
1400[ProtoNumber]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-number/index.html
1401[ProtoType]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-type/index.html
1402[ProtoIntegerType]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/index.html
1403[ProtoIntegerType.DEFAULT]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-d-e-f-a-u-l-t/index.html
1404[ProtoIntegerType.SIGNED]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-s-i-g-n-e-d/index.html
1405[ProtoIntegerType.FIXED]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/index.html
1406
1407<!--- INDEX kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema -->
1408
1409[ProtoBufSchemaGenerator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema/-proto-buf-schema-generator/index.html
1410
1411<!--- MODULE /kotlinx-serialization-cbor -->
1412<!--- INDEX kotlinx-serialization-cbor/kotlinx.serialization.cbor -->
1413
1414[Cbor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/index.html
1415[Cbor.encodeToByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/encode-to-byte-array.html
1416[Cbor.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/decode-from-byte-array.html
1417[CborBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html
1418[ByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/index.html
1419
1420<!--- END -->
1421