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