1*57b5a4a6SAndroid Build Coastguard Worker# Serialization and value classes (IR-specific) 2*57b5a4a6SAndroid Build Coastguard Worker 3*57b5a4a6SAndroid Build Coastguard WorkerThis appendix describes how value classes are handled by kotlinx.serialization. 4*57b5a4a6SAndroid Build Coastguard Worker 5*57b5a4a6SAndroid Build Coastguard Worker> Features described are available on JVM/IR (enabled by default), JS/IR and Native backends. 6*57b5a4a6SAndroid Build Coastguard Worker 7*57b5a4a6SAndroid Build Coastguard Worker**Table of contents** 8*57b5a4a6SAndroid Build Coastguard Worker 9*57b5a4a6SAndroid Build Coastguard Worker<!--- TOC --> 10*57b5a4a6SAndroid Build Coastguard Worker 11*57b5a4a6SAndroid Build Coastguard Worker* [Serializable value classes](#serializable-value-classes) 12*57b5a4a6SAndroid Build Coastguard Worker* [Unsigned types support (JSON only)](#unsigned-types-support-json-only) 13*57b5a4a6SAndroid Build Coastguard Worker* [Using value classes in your custom serializers](#using-value-classes-in-your-custom-serializers) 14*57b5a4a6SAndroid Build Coastguard Worker 15*57b5a4a6SAndroid Build Coastguard Worker<!--- END --> 16*57b5a4a6SAndroid Build Coastguard Worker 17*57b5a4a6SAndroid Build Coastguard Worker## Serializable value classes 18*57b5a4a6SAndroid Build Coastguard Worker 19*57b5a4a6SAndroid Build Coastguard WorkerWe can mark value class as serializable: 20*57b5a4a6SAndroid Build Coastguard Worker 21*57b5a4a6SAndroid Build Coastguard Worker```kotlin 22*57b5a4a6SAndroid Build Coastguard Worker@Serializable 23*57b5a4a6SAndroid Build Coastguard Worker@JvmInline 24*57b5a4a6SAndroid Build Coastguard Workervalue class Color(val rgb: Int) 25*57b5a4a6SAndroid Build Coastguard Worker``` 26*57b5a4a6SAndroid Build Coastguard Worker 27*57b5a4a6SAndroid Build Coastguard WorkerValue class in Kotlin is stored as its underlying type when possible (i.e. no boxing is required). 28*57b5a4a6SAndroid Build Coastguard WorkerSerialization framework does not impose any additional restrictions and uses the underlying type where possible as well. 29*57b5a4a6SAndroid Build Coastguard Worker 30*57b5a4a6SAndroid Build Coastguard Worker```kotlin 31*57b5a4a6SAndroid Build Coastguard Worker@Serializable 32*57b5a4a6SAndroid Build Coastguard Workerdata class NamedColor(val color: Color, val name: String) 33*57b5a4a6SAndroid Build Coastguard Worker 34*57b5a4a6SAndroid Build Coastguard Workerfun main() { 35*57b5a4a6SAndroid Build Coastguard Worker println(Json.encodeToString(NamedColor(Color(0), "black"))) 36*57b5a4a6SAndroid Build Coastguard Worker} 37*57b5a4a6SAndroid Build Coastguard Worker``` 38*57b5a4a6SAndroid Build Coastguard Worker 39*57b5a4a6SAndroid Build Coastguard WorkerIn this example, `NamedColor` is serialized as two primitives: `color: Int` and `name: String` without an allocation 40*57b5a4a6SAndroid Build Coastguard Workerof `Color` class. When we run the example, encoding data with JSON format, we get the following 41*57b5a4a6SAndroid Build Coastguard Workeroutput: 42*57b5a4a6SAndroid Build Coastguard Worker 43*57b5a4a6SAndroid Build Coastguard Worker```text 44*57b5a4a6SAndroid Build Coastguard Worker{"color": 0, "name": "black"} 45*57b5a4a6SAndroid Build Coastguard Worker``` 46*57b5a4a6SAndroid Build Coastguard Worker 47*57b5a4a6SAndroid Build Coastguard WorkerAs we see, `Color` class is not included during the encoding, only its underlying data. This invariant holds even if the actual value class 48*57b5a4a6SAndroid Build Coastguard Workeris [allocated](https://kotlinlang.org/docs/reference/inline-classes.html#representation) — for example, when value 49*57b5a4a6SAndroid Build Coastguard Workerclass is used as a generic type argument: 50*57b5a4a6SAndroid Build Coastguard Worker 51*57b5a4a6SAndroid Build Coastguard Worker```kotlin 52*57b5a4a6SAndroid Build Coastguard Worker@Serializable 53*57b5a4a6SAndroid Build Coastguard Workerclass Palette(val colors: List<Color>) 54*57b5a4a6SAndroid Build Coastguard Worker 55*57b5a4a6SAndroid Build Coastguard Workerfun main() { 56*57b5a4a6SAndroid Build Coastguard Worker println(Json.encodeToString(Palette(listOf(Color(0), Color(255), Color(128))))) 57*57b5a4a6SAndroid Build Coastguard Worker} 58*57b5a4a6SAndroid Build Coastguard Worker``` 59*57b5a4a6SAndroid Build Coastguard Worker 60*57b5a4a6SAndroid Build Coastguard WorkerThe snippet produces the following output: 61*57b5a4a6SAndroid Build Coastguard Worker 62*57b5a4a6SAndroid Build Coastguard Worker```text 63*57b5a4a6SAndroid Build Coastguard Worker{"colors":[0, 255, 128]} 64*57b5a4a6SAndroid Build Coastguard Worker``` 65*57b5a4a6SAndroid Build Coastguard Worker 66*57b5a4a6SAndroid Build Coastguard Worker## Unsigned types support (JSON only) 67*57b5a4a6SAndroid Build Coastguard Worker 68*57b5a4a6SAndroid Build Coastguard WorkerKotlin standard library provides ready-to-use unsigned arithmetics, leveraging value classes 69*57b5a4a6SAndroid Build Coastguard Workerto represent unsigned types: `UByte`, `UShort`, `UInt` and `ULong`. 70*57b5a4a6SAndroid Build Coastguard Worker[Json] format has built-in support for them: these types are serialized as theirs string 71*57b5a4a6SAndroid Build Coastguard Workerrepresentations in unsigned form. 72*57b5a4a6SAndroid Build Coastguard WorkerThese types are handled as regular serializable types by the compiler plugin and can be freely used in serializable classes: 73*57b5a4a6SAndroid Build Coastguard Worker 74*57b5a4a6SAndroid Build Coastguard Worker```kotlin 75*57b5a4a6SAndroid Build Coastguard Worker@Serializable 76*57b5a4a6SAndroid Build Coastguard Workerclass Counter(val counted: UByte, val description: String) 77*57b5a4a6SAndroid Build Coastguard Worker 78*57b5a4a6SAndroid Build Coastguard Workerfun main() { 79*57b5a4a6SAndroid Build Coastguard Worker val counted = 239.toUByte() 80*57b5a4a6SAndroid Build Coastguard Worker println(Json.encodeToString(Counter(counted, "tries"))) 81*57b5a4a6SAndroid Build Coastguard Worker} 82*57b5a4a6SAndroid Build Coastguard Worker``` 83*57b5a4a6SAndroid Build Coastguard Worker 84*57b5a4a6SAndroid Build Coastguard WorkerThe output is following: 85*57b5a4a6SAndroid Build Coastguard Worker 86*57b5a4a6SAndroid Build Coastguard Worker```text 87*57b5a4a6SAndroid Build Coastguard Worker{"counted":239,"description":"tries"} 88*57b5a4a6SAndroid Build Coastguard Worker``` 89*57b5a4a6SAndroid Build Coastguard Worker 90*57b5a4a6SAndroid Build Coastguard Worker> Unsigned types are currently supported only in JSON format. Other formats such as ProtoBuf and CBOR 91*57b5a4a6SAndroid Build Coastguard Worker> use built-in serializers that use an underlying signed representation for unsigned types. 92*57b5a4a6SAndroid Build Coastguard Worker 93*57b5a4a6SAndroid Build Coastguard Worker## Using value classes in your custom serializers 94*57b5a4a6SAndroid Build Coastguard Worker 95*57b5a4a6SAndroid Build Coastguard WorkerLet's return to our `NamedColor` example and try to write a custom serializer for it. Normally, as shown 96*57b5a4a6SAndroid Build Coastguard Workerin [Hand-written composite serializer](serializers.md#hand-written-composite-serializer), we would write the following code 97*57b5a4a6SAndroid Build Coastguard Workerin `serialize` method: 98*57b5a4a6SAndroid Build Coastguard Worker 99*57b5a4a6SAndroid Build Coastguard Worker```kotlin 100*57b5a4a6SAndroid Build Coastguard Workeroverride fun serialize(encoder: Encoder, value: NamedColor) { 101*57b5a4a6SAndroid Build Coastguard Worker encoder.encodeStructure(descriptor) { 102*57b5a4a6SAndroid Build Coastguard Worker encodeSerializableElement(descriptor, 0, Color.serializer(), value.color) 103*57b5a4a6SAndroid Build Coastguard Worker encodeStringElement(descriptor, 1, value.name) 104*57b5a4a6SAndroid Build Coastguard Worker } 105*57b5a4a6SAndroid Build Coastguard Worker} 106*57b5a4a6SAndroid Build Coastguard Worker``` 107*57b5a4a6SAndroid Build Coastguard Worker 108*57b5a4a6SAndroid Build Coastguard WorkerHowever, since `Color` is used as a type argument in [encodeSerializableElement][CompositeEncoder.encodeSerializableElement] function, `value.color` will be boxed 109*57b5a4a6SAndroid Build Coastguard Workerto `Color` wrapper before passing it to the function, preventing the value class optimization. To avoid this, we can use 110*57b5a4a6SAndroid Build Coastguard Workerspecial [encodeInlineElement][CompositeEncoder.encodeInlineElement] function instead. It uses [serial descriptor][SerialDescriptor] of `Color` ([retrieved][SerialDescriptor.getElementDescriptor] from serial descriptor of `NamedColor`) instead of [KSerializer], 111*57b5a4a6SAndroid Build Coastguard Workerdoes not have type parameters and does not accept any values. Instead, it returns [Encoder]. Using it, we can encode 112*57b5a4a6SAndroid Build Coastguard Workerunboxed value: 113*57b5a4a6SAndroid Build Coastguard Worker 114*57b5a4a6SAndroid Build Coastguard Worker```kotlin 115*57b5a4a6SAndroid Build Coastguard Workeroverride fun serialize(encoder: Encoder, value: NamedColor) { 116*57b5a4a6SAndroid Build Coastguard Worker encoder.encodeStructure(descriptor) { 117*57b5a4a6SAndroid Build Coastguard Worker encodeInlineElement(descriptor, 0).encodeInt(value.color) 118*57b5a4a6SAndroid Build Coastguard Worker encodeStringElement(descriptor, 1, value.name) 119*57b5a4a6SAndroid Build Coastguard Worker } 120*57b5a4a6SAndroid Build Coastguard Worker} 121*57b5a4a6SAndroid Build Coastguard Worker``` 122*57b5a4a6SAndroid Build Coastguard Worker 123*57b5a4a6SAndroid Build Coastguard WorkerThe same principle goes also with [CompositeDecoder]: it has [decodeInlineElement][CompositeDecoder.decodeInlineElement] function that returns [Decoder]. 124*57b5a4a6SAndroid Build Coastguard Worker 125*57b5a4a6SAndroid Build Coastguard WorkerIf your class should be represented as a primitive (as shown in [Primitive serializer](serializers.md#primitive-serializer) section), 126*57b5a4a6SAndroid Build Coastguard Workerand you cannot use [encodeStructure][Encoder.encodeStructure] function, there is a complementary function in [Encoder] called [encodeInline][Encoder.encodeInline]. 127*57b5a4a6SAndroid Build Coastguard WorkerWe will use it to show an example how one can represent a class as an unsigned integer. 128*57b5a4a6SAndroid Build Coastguard Worker 129*57b5a4a6SAndroid Build Coastguard WorkerLet's start with a UID class: 130*57b5a4a6SAndroid Build Coastguard Worker 131*57b5a4a6SAndroid Build Coastguard Worker```kotlin 132*57b5a4a6SAndroid Build Coastguard Worker@Serializable(UIDSerializer::class) 133*57b5a4a6SAndroid Build Coastguard Workerclass UID(val uid: Int) 134*57b5a4a6SAndroid Build Coastguard Worker``` 135*57b5a4a6SAndroid Build Coastguard Worker 136*57b5a4a6SAndroid Build Coastguard Worker`uid` type is `Int`, but suppose we want it to be an unsigned integer in JSON. We can start writing the 137*57b5a4a6SAndroid Build Coastguard Workerfollowing custom serializer: 138*57b5a4a6SAndroid Build Coastguard Worker 139*57b5a4a6SAndroid Build Coastguard Worker```kotlin 140*57b5a4a6SAndroid Build Coastguard Workerobject UIDSerializer: KSerializer<UID> { 141*57b5a4a6SAndroid Build Coastguard Worker override val descriptor = UInt.serializer().descriptor 142*57b5a4a6SAndroid Build Coastguard Worker} 143*57b5a4a6SAndroid Build Coastguard Worker``` 144*57b5a4a6SAndroid Build Coastguard Worker 145*57b5a4a6SAndroid Build Coastguard WorkerNote that we are using here descriptor from `UInt.serializer()` — it means that the class' representation looks like a 146*57b5a4a6SAndroid Build Coastguard WorkerUInt's one. 147*57b5a4a6SAndroid Build Coastguard Worker 148*57b5a4a6SAndroid Build Coastguard WorkerThen the `serialize` method: 149*57b5a4a6SAndroid Build Coastguard Worker 150*57b5a4a6SAndroid Build Coastguard Worker```kotlin 151*57b5a4a6SAndroid Build Coastguard Workeroverride fun serialize(encoder: Encoder, value: UID) { 152*57b5a4a6SAndroid Build Coastguard Worker encoder.encodeInline(descriptor).encodeInt(value.uid) 153*57b5a4a6SAndroid Build Coastguard Worker} 154*57b5a4a6SAndroid Build Coastguard Worker``` 155*57b5a4a6SAndroid Build Coastguard Worker 156*57b5a4a6SAndroid Build Coastguard WorkerThat's where the magic happens — despite we called a regular [encodeInt][Encoder.encodeInt] with a `uid: Int` argument, the output will contain 157*57b5a4a6SAndroid Build Coastguard Workeran unsigned int because of the special encoder from `encodeInline` function. Since JSON format supports unsigned integers, it 158*57b5a4a6SAndroid Build Coastguard Workerrecognizes theirs descriptors when they're passed into `encodeInline` and handles consecutive calls as for unsigned integers. 159*57b5a4a6SAndroid Build Coastguard Worker 160*57b5a4a6SAndroid Build Coastguard WorkerThe `deserialize` method looks symmetrically: 161*57b5a4a6SAndroid Build Coastguard Worker 162*57b5a4a6SAndroid Build Coastguard Worker```kotlin 163*57b5a4a6SAndroid Build Coastguard Workeroverride fun deserialize(decoder: Decoder): UID { 164*57b5a4a6SAndroid Build Coastguard Worker return UID(decoder.decodeInline(descriptor).decodeInt()) 165*57b5a4a6SAndroid Build Coastguard Worker} 166*57b5a4a6SAndroid Build Coastguard Worker``` 167*57b5a4a6SAndroid Build Coastguard Worker 168*57b5a4a6SAndroid Build Coastguard Worker> Disclaimer: You can also write such a serializer for value class itself (imagine UID being the value class — there's no need to change anything in the serializer). 169*57b5a4a6SAndroid Build Coastguard Worker> However, do not use anything in custom serializers for value classes besides `encodeInline`. As we discussed, calls to value class serializer may be 170*57b5a4a6SAndroid Build Coastguard Worker> optimized and replaced with a `encodeInlineElement` calls. 171*57b5a4a6SAndroid Build Coastguard Worker> `encodeInline` and `encodeInlineElement` calls with the same descriptor are considered equivalent and can be replaced with each other — formats should return the same `Encoder`. 172*57b5a4a6SAndroid Build Coastguard Worker> If you embed custom logic in custom value class serializer, you may get different results depending on whether this serializer was called at all 173*57b5a4a6SAndroid Build Coastguard Worker> (and this, in turn, depends on whether value class was boxed or not). 174*57b5a4a6SAndroid Build Coastguard Worker 175*57b5a4a6SAndroid Build Coastguard Worker--- 176*57b5a4a6SAndroid Build Coastguard Worker 177*57b5a4a6SAndroid Build Coastguard Worker<!--- MODULE /kotlinx-serialization-core --> 178*57b5a4a6SAndroid Build Coastguard Worker<!--- INDEX kotlinx-serialization-core/kotlinx.serialization --> 179*57b5a4a6SAndroid Build Coastguard Worker 180*57b5a4a6SAndroid Build Coastguard Worker[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html 181*57b5a4a6SAndroid Build Coastguard Worker 182*57b5a4a6SAndroid Build Coastguard Worker<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding --> 183*57b5a4a6SAndroid Build Coastguard Worker 184*57b5a4a6SAndroid Build Coastguard Worker[CompositeEncoder.encodeSerializableElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-serializable-element.html 185*57b5a4a6SAndroid Build Coastguard Worker[CompositeEncoder.encodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-inline-element.html 186*57b5a4a6SAndroid Build Coastguard Worker[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html 187*57b5a4a6SAndroid Build Coastguard Worker[CompositeDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html 188*57b5a4a6SAndroid Build Coastguard Worker[CompositeDecoder.decodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-inline-element.html 189*57b5a4a6SAndroid Build Coastguard Worker[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html 190*57b5a4a6SAndroid Build Coastguard Worker[Encoder.encodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html 191*57b5a4a6SAndroid Build Coastguard Worker[Encoder.encodeInline]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-inline.html 192*57b5a4a6SAndroid Build Coastguard Worker[Encoder.encodeInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-int.html 193*57b5a4a6SAndroid Build Coastguard Worker 194*57b5a4a6SAndroid Build Coastguard Worker<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.descriptors --> 195*57b5a4a6SAndroid Build Coastguard Worker 196*57b5a4a6SAndroid Build Coastguard Worker[SerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html 197*57b5a4a6SAndroid Build Coastguard Worker[SerialDescriptor.getElementDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/get-element-descriptor.html 198*57b5a4a6SAndroid Build Coastguard Worker 199*57b5a4a6SAndroid Build Coastguard Worker<!--- MODULE /kotlinx-serialization-json --> 200*57b5a4a6SAndroid Build Coastguard Worker<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json --> 201*57b5a4a6SAndroid Build Coastguard Worker 202*57b5a4a6SAndroid Build Coastguard Worker[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html 203*57b5a4a6SAndroid Build Coastguard Worker 204*57b5a4a6SAndroid Build Coastguard Worker<!--- END --> 205