• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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] &mdash; 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] &mdash; returns the next value from the list.
864* [decodeElementIndex][CompositeDecoder.decodeElementIndex] &mdash; 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] &mdash; 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