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