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