Skip to content

Commit 0697421

Browse files
sandwwraithqwwdfsadpdvrieze
authored
Add guide for delegating serializers and wrapping serial descriptor (Kotlin#1591)
Fixes Kotlin#1588 Co-authored-by: Vsevolod Tolstopyatov <[email protected]> Co-authored-by: Paul de Vrieze <[email protected]>
1 parent 656ba0c commit 0697421

17 files changed

+284
-175
lines changed

docs/formats.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1098,7 +1098,7 @@ In the encoder this is achieved by overriding the
10981098
In our `DataOutput` format example we might want to provide a specialized efficient data path for serializing an array
10991099
of bytes since [DataOutput][java.io.DataOutput] has a special method for this purpose.
11001100

1101-
Detection of the type is performed by looking at the `serializer`, not by checking the type of the `value`
1101+
Detection of the type is performed by looking at the `serializer.descriptor`, not by checking the type of the `value`
11021102
being serialized, so we fetch the builtin [KSerializer] instance for `ByteArray` type.
11031103

11041104
> This an important difference. This way our format implementation properly supports
@@ -1151,7 +1151,7 @@ class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
11511151

11521152
```kotlin
11531153
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
1154-
if (serializer === byteArraySerializer)
1154+
if (serializer.descriptor == byteArraySerializer.descriptor)
11551155
encodeByteArray(value as ByteArray)
11561156
else
11571157
super.encodeSerializableValue(serializer, value)
@@ -1218,7 +1218,7 @@ the [decodeSerializableValue][Decoder.decodeSerializableValue] function.
12181218
```kotlin
12191219
@Suppress("UNCHECKED_CAST")
12201220
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T =
1221-
if (deserializer === byteArraySerializer)
1221+
if (deserializer.descriptor == byteArraySerializer.descriptor)
12221222
decodeByteArray() as T
12231223
else
12241224
super.decodeSerializableValue(deserializer, previousValue)

docs/serialization-guide.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Once the project is set up, we can start serializing some classes.
6262
* <a name='using-top-level-serializer-function'></a>[Using top-level serializer function](serializers.md#using-top-level-serializer-function)
6363
* <a name='custom-serializers'></a>[Custom serializers](serializers.md#custom-serializers)
6464
* <a name='primitive-serializer'></a>[Primitive serializer](serializers.md#primitive-serializer)
65+
* <a name='delegating-serializers'></a>[Delegating serializers](serializers.md#delegating-serializers)
6566
* <a name='composite-serializer-via-surrogate'></a>[Composite serializer via surrogate](serializers.md#composite-serializer-via-surrogate)
6667
* <a name='hand-written-composite-serializer'></a>[Hand-written composite serializer](serializers.md#hand-written-composite-serializer)
6768
* <a name='sequential-decoding-protocol-experimental'></a>[Sequential decoding protocol (experimental)](serializers.md#sequential-decoding-protocol-experimental)

docs/serializers.md

+81-16
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ In this chapter we'll take a look at serializers in more detail, and we'll see h
1717
* [Using top-level serializer function](#using-top-level-serializer-function)
1818
* [Custom serializers](#custom-serializers)
1919
* [Primitive serializer](#primitive-serializer)
20+
* [Delegating serializers](#delegating-serializers)
2021
* [Composite serializer via surrogate](#composite-serializer-via-surrogate)
2122
* [Hand-written composite serializer](#hand-written-composite-serializer)
2223
* [Sequential decoding protocol (experimental)](#sequential-decoding-protocol-experimental)
@@ -377,6 +378,72 @@ Both `Color` properties are serialized as strings.
377378

378379
<!--- TEST -->
379380

381+
### Delegating serializers
382+
383+
In the previous example, we represented the `Color` class as a string.
384+
String is considered to be a primitive type, therefore we used `PrimitiveClassDescriptor` and specialized `encodeString` method.
385+
Now let's see what our actions would be if we have to serialize `Color` as another non-primitive type, let's say `IntArray`.
386+
387+
An implementation of [KSerializer] for our original `Color` class is going to perform a conversion between
388+
`Color` and `IntArray`, but delegate the actual serialization logic to the `IntArraySerializer`
389+
using [encodeSerializableValue][Encoder.encodeSerializableValue] and
390+
[decodeSerializableValue][Decoder.decodeSerializableValue].
391+
392+
```kotlin
393+
import kotlinx.serialization.builtins.IntArraySerializer
394+
395+
class ColorIntArraySerializer : KSerializer<Color> {
396+
private val delegateSerializer = IntArraySerializer()
397+
override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor)
398+
399+
override fun serialize(encoder: Encoder, value: Color) {
400+
val data = intArrayOf(
401+
(value.rgb shr 16) and 0xFF,
402+
(value.rgb shr 8) and 0xFF,
403+
value.rgb and 0xFF
404+
)
405+
encoder.encodeSerializableValue(delegateSerializer, data)
406+
}
407+
408+
override fun deserialize(decoder: Decoder): Color {
409+
val array = decoder.decodeSerializableValue(delegateSerializer)
410+
return Color((array[0] shl 16) or (array[1] shl 8) or array[2])
411+
}
412+
}
413+
```
414+
415+
Note that we can't use default `Color.serializer().descriptor` here because formats that rely
416+
on the schema may think that we would call `encodeInt` instead of `encodeSerializableValue`.
417+
Neither we can use `IntArraySerializer().descriptor` directly — otherwise, formats that handle int arrays specially
418+
can't tell if `value` is really a `IntArray` or a `Color`. Don't worry, this optimization would still kick in
419+
when serializing actual underlying int array.
420+
421+
> Example of how format can treat arrays specially is shown in the [formats guide](formats.md#format-specific-types).
422+
423+
Now we can use the serializer:
424+
425+
```kotlin
426+
@Serializable(with = ColorIntArraySerializer::class)
427+
class Color(val rgb: Int)
428+
429+
fun main() {
430+
val green = Color(0x00ff00)
431+
println(Json.encodeToString(green))
432+
}
433+
```
434+
435+
As you can see, such array representation is not very useful in JSON,
436+
but may save some space when used with a `ByteArray` and a binary format.
437+
438+
> You can get the full code [here](../guide/example/example-serializer-10.kt).
439+
440+
```text
441+
[0,255,0]
442+
```
443+
444+
<!--- TEST -->
445+
446+
380447
### Composite serializer via surrogate
381448

382449
Now our challenge is to get `Color` serialized so that it is represented in JSON as if it is a class
@@ -403,11 +470,9 @@ private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
403470
Now we can use the `ColorSurrogate.serializer()` function to retrieve a plugin-generated serializer for the
404471
surrogate class.
405472

406-
An implementation of [KSerializer] for our original `Color` class is going to perform a conversion between
407-
`Color` and `ColorSurrogate`, but delegate the actual serialization logic to the `ColorSurrogate.serializer()`
408-
using [encodeSerializableValue][Encoder.encodeSerializableValue] and
409-
[decodeSerializableValue][Decoder.decodeSerializableValue], fully reusing an automatically
410-
generated [SerialDescriptor] for the surrogate.
473+
We can use the same approach as in [delegating serializer](#delegating-serializers), but this time,
474+
we are fully reusing an automatically
475+
generated [SerialDescriptor] for the surrogate because it should be indistinguishable from the original.
411476

412477
```kotlin
413478
object ColorSerializer : KSerializer<Color> {
@@ -441,7 +506,7 @@ fun main() {
441506
}
442507
-->
443508

444-
> You can get the full code [here](../guide/example/example-serializer-10.kt).
509+
> You can get the full code [here](../guide/example/example-serializer-11.kt).
445510
446511
```text
447512
{"r":0,"g":255,"b":0}
@@ -535,7 +600,7 @@ fun main() {
535600
}
536601
```
537602
538-
> You can get the full code [here](../guide/example/example-serializer-11.kt).
603+
> You can get the full code [here](../guide/example/example-serializer-12.kt).
539604
540605
As before, we got the `Color` class represented as a JSON object with three keys:
541606
@@ -609,7 +674,7 @@ fun main() {
609674
}
610675
-->
611676
612-
> You can get the full code [here](../guide/example/example-serializer-12.kt).
677+
> You can get the full code [here](../guide/example/example-serializer-13.kt).
613678
614679
<!--- TEST
615680
{"r":0,"g":255,"b":0}
@@ -656,7 +721,7 @@ fun main() {
656721
}
657722
```
658723

659-
> You can get the full code [here](../guide/example/example-serializer-13.kt).
724+
> You can get the full code [here](../guide/example/example-serializer-14.kt).
660725

661726
```text
662727
1455494400000
@@ -694,7 +759,7 @@ fun main() {
694759
}
695760
```
696761

697-
> You can get the full code [here](../guide/example/example-serializer-14.kt).
762+
> You can get the full code [here](../guide/example/example-serializer-15.kt).
698763

699764
The `stableReleaseDate` property is serialized with the serialization strategy that we specified for it:
700765

@@ -737,7 +802,7 @@ fun main() {
737802
println(Json.encodeToString(data))
738803
}
739804
```
740-
> You can get the full code [here](../guide/example/example-serializer-15.kt).
805+
> You can get the full code [here](../guide/example/example-serializer-16.kt).
741806

742807
```text
743808
{"name":"Kotlin","stableReleaseDate":1455494400000}
@@ -785,7 +850,7 @@ fun main() {
785850
}
786851
```
787852
788-
> You can get the full code [here](../guide/example/example-serializer-16.kt).
853+
> You can get the full code [here](../guide/example/example-serializer-17.kt).
789854
790855
The resulting JSON looks like the `Project` class was serialized directly.
791856
@@ -849,7 +914,7 @@ fun main() {
849914
To actually serialize this class we must provide the corresponding context when calling the `encodeToXxx`/`decodeFromXxx`
850915
functions. Without it we'll get a "Serializer for class 'Date' is not found" exception.
851916

852-
> See [here](../guide/example/example-serializer-17.kt) for an example that produces that exception.
917+
> See [here](../guide/example/example-serializer-18.kt) for an example that produces that exception.
853918

854919
<!--- TEST LINES_START
855920
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.
@@ -908,7 +973,7 @@ fun main() {
908973
}
909974
```
910975

911-
> You can get the full code [here](../guide/example/example-serializer-18.kt).
976+
> You can get the full code [here](../guide/example/example-serializer-19.kt).
912977
```text
913978
{"name":"Kotlin","stableReleaseDate":1455494400000}
914979
```
@@ -967,7 +1032,7 @@ fun main() {
9671032
}
9681033
```
9691034

970-
> You can get the full code [here](../guide/example/example-serializer-19.kt).
1035+
> You can get the full code [here](../guide/example/example-serializer-20.kt).
9711036

9721037
This gets all the `Project` properties serialized:
9731038

@@ -1008,7 +1073,7 @@ fun main() {
10081073
}
10091074
```
10101075

1011-
> You can get the full code [here](../guide/example/example-serializer-20.kt).
1076+
> You can get the full code [here](../guide/example/example-serializer-21.kt).
10121077

10131078
The output is shown below.
10141079

guide/example/example-formats-15.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
3131
override fun encodeNotNullMark() = encodeBoolean(true)
3232

3333
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
34-
if (serializer === byteArraySerializer)
34+
if (serializer.descriptor == byteArraySerializer.descriptor)
3535
encodeByteArray(value as ByteArray)
3636
else
3737
super.encodeSerializableValue(serializer, value)
@@ -90,7 +90,7 @@ class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : Abstr
9090

9191
@Suppress("UNCHECKED_CAST")
9292
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T =
93-
if (deserializer === byteArraySerializer)
93+
if (deserializer.descriptor == byteArraySerializer.descriptor)
9494
decodeByteArray() as T
9595
else
9696
super.decodeSerializableValue(deserializer, previousValue)

guide/example/example-serializer-10.kt

+15-15
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,31 @@ import kotlinx.serialization.json.*
66
import kotlinx.serialization.encoding.*
77
import kotlinx.serialization.descriptors.*
88

9-
@Serializable
10-
@SerialName("Color")
11-
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
12-
init {
13-
require(r in 0..255 && g in 0..255 && b in 0..255)
14-
}
15-
}
9+
import kotlinx.serialization.builtins.IntArraySerializer
1610

17-
object ColorSerializer : KSerializer<Color> {
18-
override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor
11+
class ColorIntArraySerializer : KSerializer<Color> {
12+
private val delegateSerializer = IntArraySerializer()
13+
override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor)
1914

2015
override fun serialize(encoder: Encoder, value: Color) {
21-
val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff)
22-
encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
16+
val data = intArrayOf(
17+
(value.rgb shr 16) and 0xFF,
18+
(value.rgb shr 8) and 0xFF,
19+
value.rgb and 0xFF
20+
)
21+
encoder.encodeSerializableValue(delegateSerializer, data)
2322
}
2423

2524
override fun deserialize(decoder: Decoder): Color {
26-
val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
27-
return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
25+
val array = decoder.decodeSerializableValue(delegateSerializer)
26+
return Color((array[0] shl 16) or (array[1] shl 8) or array[2])
2827
}
2928
}
3029

31-
@Serializable(with = ColorSerializer::class)
30+
@Serializable(with = ColorIntArraySerializer::class)
3231
class Color(val rgb: Int)
32+
3333
fun main() {
3434
val green = Color(0x00ff00)
3535
println(Json.encodeToString(green))
36-
}
36+
}

guide/example/example-serializer-11.kt

+22-38
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,31 @@ import kotlinx.serialization.json.*
66
import kotlinx.serialization.encoding.*
77
import kotlinx.serialization.descriptors.*
88

9-
object ColorAsObjectSerializer : KSerializer<Color> {
9+
@Serializable
10+
@SerialName("Color")
11+
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
12+
init {
13+
require(r in 0..255 && g in 0..255 && b in 0..255)
14+
}
15+
}
1016

11-
override val descriptor: SerialDescriptor =
12-
buildClassSerialDescriptor("Color") {
13-
element<Int>("r")
14-
element<Int>("g")
15-
element<Int>("b")
16-
}
17+
object ColorSerializer : KSerializer<Color> {
18+
override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor
1719

18-
override fun serialize(encoder: Encoder, value: Color) =
19-
encoder.encodeStructure(descriptor) {
20-
encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff)
21-
encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff)
22-
encodeIntElement(descriptor, 2, value.rgb and 0xff)
23-
}
20+
override fun serialize(encoder: Encoder, value: Color) {
21+
val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff)
22+
encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
23+
}
2424

25-
override fun deserialize(decoder: Decoder): Color =
26-
decoder.decodeStructure(descriptor) {
27-
var r = -1
28-
var g = -1
29-
var b = -1
30-
while (true) {
31-
when (val index = decodeElementIndex(descriptor)) {
32-
0 -> r = decodeIntElement(descriptor, 0)
33-
1 -> g = decodeIntElement(descriptor, 1)
34-
2 -> b = decodeIntElement(descriptor, 2)
35-
CompositeDecoder.DECODE_DONE -> break
36-
else -> error("Unexpected index: $index")
37-
}
38-
}
39-
require(r in 0..255 && g in 0..255 && b in 0..255)
40-
Color((r shl 16) or (g shl 8) or b)
41-
}
25+
override fun deserialize(decoder: Decoder): Color {
26+
val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
27+
return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
28+
}
4229
}
4330

44-
@Serializable(with = ColorAsObjectSerializer::class)
45-
data class Color(val rgb: Int)
46-
31+
@Serializable(with = ColorSerializer::class)
32+
class Color(val rgb: Int)
4733
fun main() {
48-
val color = Color(0x00ff00)
49-
val string = Json.encodeToString(color)
50-
println(string)
51-
require(Json.decodeFromString<Color>(string) == color)
52-
}
34+
val green = Color(0x00ff00)
35+
println(Json.encodeToString(green))
36+
}

guide/example/example-serializer-12.kt

+3-7
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,8 @@ object ColorAsObjectSerializer : KSerializer<Color> {
2626
decoder.decodeStructure(descriptor) {
2727
var r = -1
2828
var g = -1
29-
var b = -1
30-
if (decodeSequentially()) { // sequential decoding protocol
31-
r = decodeIntElement(descriptor, 0)
32-
g = decodeIntElement(descriptor, 1)
33-
b = decodeIntElement(descriptor, 2)
34-
} else while (true) {
29+
var b = -1
30+
while (true) {
3531
when (val index = decodeElementIndex(descriptor)) {
3632
0 -> r = decodeIntElement(descriptor, 0)
3733
1 -> g = decodeIntElement(descriptor, 1)
@@ -43,7 +39,7 @@ object ColorAsObjectSerializer : KSerializer<Color> {
4339
require(r in 0..255 && g in 0..255 && b in 0..255)
4440
Color((r shl 16) or (g shl 8) or b)
4541
}
46-
}
42+
}
4743

4844
@Serializable(with = ColorAsObjectSerializer::class)
4945
data class Color(val rgb: Int)

0 commit comments

Comments
 (0)