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] — 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] — returns the next value from the list. 646* [decodeElementIndex][CompositeDecoder.decodeElementIndex] — 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] — 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