From c35e86ffe814c418688617fe21d0a67b7a0fab87 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 11 Jul 2024 13:43:52 +0200 Subject: [PATCH 01/14] WIP restructuring serialization docs --- docs/cfg/buildprofiles.xml | 10 + docs/images/icons/icon-1-done.svg | 4 + docs/images/icons/icon-1.svg | 4 + docs/images/icons/icon-2-done.svg | 4 + docs/images/icons/icon-2-todo.svg | 4 + docs/images/icons/icon-2.svg | 4 + docs/images/icons/icon-3-done.svg | 4 + docs/images/icons/icon-3-todo.svg | 4 + docs/images/icons/icon-3.svg | 4 + docs/images/icons/icon-4-done.svg | 4 + docs/images/icons/icon-4-todo.svg | 4 + docs/images/icons/icon-4.svg | 4 + docs/images/icons/icon-5-done.svg | 4 + docs/images/icons/icon-5-todo.svg | 4 + docs/images/icons/icon-5.svg | 4 + docs/images/icons/icon-6-done.svg | 4 + docs/images/icons/icon-6-todo.svg | 4 + docs/images/icons/icon-6.svg | 4 + docs/images/icons/icon-7-todo.svg | 4 + docs/images/icons/icon-7.svg | 4 + docs/images/serialization.svg | 13 + docs/project.ihp | 14 + docs/serialization.tree | 17 + docs/{ => topics}/basic-serialization.md | 38 +- docs/{ => topics}/building.md | 0 docs/{ => topics}/builtin-classes.md | 26 +- docs/{ => topics}/compatibility.md | 0 docs/topics/create-custom-serializers.md | 25 + docs/{ => topics}/formats.md | 34 +- docs/{ => topics}/inline-classes.md | 0 docs/{ => topics}/json.md | 60 +- docs/{ => topics}/knit.properties | 0 docs/{ => topics}/migration.md | 2 +- docs/{ => topics}/polymorphism.md | 40 +- .../serialization-customization-options.md | 524 ++++++++++++++++++ .../serialization-get-started-create.md | 170 ++++++ .../serialization-get-started-overview.md | 19 + .../serialization-get-started-serialize.md | 49 ++ docs/topics/serialization-get-started.md | 272 +++++++++ docs/{ => topics}/serialization-guide.md | 2 +- docs/topics/serialization.md | 110 ++++ docs/{ => topics}/serializers.md | 46 +- docs/topics/value-classes.md | 204 +++++++ docs/v.list | 7 + 44 files changed, 1634 insertions(+), 124 deletions(-) create mode 100644 docs/cfg/buildprofiles.xml create mode 100644 docs/images/icons/icon-1-done.svg create mode 100644 docs/images/icons/icon-1.svg create mode 100644 docs/images/icons/icon-2-done.svg create mode 100644 docs/images/icons/icon-2-todo.svg create mode 100644 docs/images/icons/icon-2.svg create mode 100644 docs/images/icons/icon-3-done.svg create mode 100644 docs/images/icons/icon-3-todo.svg create mode 100644 docs/images/icons/icon-3.svg create mode 100644 docs/images/icons/icon-4-done.svg create mode 100644 docs/images/icons/icon-4-todo.svg create mode 100644 docs/images/icons/icon-4.svg create mode 100644 docs/images/icons/icon-5-done.svg create mode 100644 docs/images/icons/icon-5-todo.svg create mode 100644 docs/images/icons/icon-5.svg create mode 100644 docs/images/icons/icon-6-done.svg create mode 100644 docs/images/icons/icon-6-todo.svg create mode 100644 docs/images/icons/icon-6.svg create mode 100644 docs/images/icons/icon-7-todo.svg create mode 100644 docs/images/icons/icon-7.svg create mode 100644 docs/images/serialization.svg create mode 100644 docs/project.ihp create mode 100644 docs/serialization.tree rename docs/{ => topics}/basic-serialization.md (93%) rename docs/{ => topics}/building.md (100%) rename docs/{ => topics}/builtin-classes.md (92%) rename docs/{ => topics}/compatibility.md (100%) create mode 100644 docs/topics/create-custom-serializers.md rename docs/{ => topics}/formats.md (97%) rename docs/{ => topics}/inline-classes.md (100%) rename docs/{ => topics}/json.md (96%) rename docs/{ => topics}/knit.properties (100%) rename docs/{ => topics}/migration.md (93%) rename docs/{ => topics}/polymorphism.md (95%) create mode 100644 docs/topics/serialization-customization-options.md create mode 100644 docs/topics/serialization-get-started-create.md create mode 100644 docs/topics/serialization-get-started-overview.md create mode 100644 docs/topics/serialization-get-started-serialize.md create mode 100644 docs/topics/serialization-get-started.md rename docs/{ => topics}/serialization-guide.md (99%) create mode 100644 docs/topics/serialization.md rename docs/{ => topics}/serializers.md (96%) create mode 100644 docs/topics/value-classes.md create mode 100644 docs/v.list diff --git a/docs/cfg/buildprofiles.xml b/docs/cfg/buildprofiles.xml new file mode 100644 index 0000000000..f4123a1131 --- /dev/null +++ b/docs/cfg/buildprofiles.xml @@ -0,0 +1,10 @@ + + + + + true + https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/ + true + + + diff --git a/docs/images/icons/icon-1-done.svg b/docs/images/icons/icon-1-done.svg new file mode 100644 index 0000000000..ef2d7c50a1 --- /dev/null +++ b/docs/images/icons/icon-1-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-1.svg b/docs/images/icons/icon-1.svg new file mode 100644 index 0000000000..5316ebc265 --- /dev/null +++ b/docs/images/icons/icon-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-2-done.svg b/docs/images/icons/icon-2-done.svg new file mode 100644 index 0000000000..b77d5c47c9 --- /dev/null +++ b/docs/images/icons/icon-2-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-2-todo.svg b/docs/images/icons/icon-2-todo.svg new file mode 100644 index 0000000000..de373b82c3 --- /dev/null +++ b/docs/images/icons/icon-2-todo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-2.svg b/docs/images/icons/icon-2.svg new file mode 100644 index 0000000000..ad2c4ec448 --- /dev/null +++ b/docs/images/icons/icon-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-3-done.svg b/docs/images/icons/icon-3-done.svg new file mode 100644 index 0000000000..d09c6e8b93 --- /dev/null +++ b/docs/images/icons/icon-3-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-3-todo.svg b/docs/images/icons/icon-3-todo.svg new file mode 100644 index 0000000000..8c715a0351 --- /dev/null +++ b/docs/images/icons/icon-3-todo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-3.svg b/docs/images/icons/icon-3.svg new file mode 100644 index 0000000000..e934f21a7f --- /dev/null +++ b/docs/images/icons/icon-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-4-done.svg b/docs/images/icons/icon-4-done.svg new file mode 100644 index 0000000000..ea096e0e3a --- /dev/null +++ b/docs/images/icons/icon-4-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-4-todo.svg b/docs/images/icons/icon-4-todo.svg new file mode 100644 index 0000000000..91a1fd8c65 --- /dev/null +++ b/docs/images/icons/icon-4-todo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-4.svg b/docs/images/icons/icon-4.svg new file mode 100644 index 0000000000..8558746c2d --- /dev/null +++ b/docs/images/icons/icon-4.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-5-done.svg b/docs/images/icons/icon-5-done.svg new file mode 100644 index 0000000000..ce264804a3 --- /dev/null +++ b/docs/images/icons/icon-5-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-5-todo.svg b/docs/images/icons/icon-5-todo.svg new file mode 100644 index 0000000000..47be40b199 --- /dev/null +++ b/docs/images/icons/icon-5-todo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-5.svg b/docs/images/icons/icon-5.svg new file mode 100644 index 0000000000..ced6626703 --- /dev/null +++ b/docs/images/icons/icon-5.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-6-done.svg b/docs/images/icons/icon-6-done.svg new file mode 100644 index 0000000000..513defa5b2 --- /dev/null +++ b/docs/images/icons/icon-6-done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-6-todo.svg b/docs/images/icons/icon-6-todo.svg new file mode 100644 index 0000000000..96855e9235 --- /dev/null +++ b/docs/images/icons/icon-6-todo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-6.svg b/docs/images/icons/icon-6.svg new file mode 100644 index 0000000000..39e537598a --- /dev/null +++ b/docs/images/icons/icon-6.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-7-todo.svg b/docs/images/icons/icon-7-todo.svg new file mode 100644 index 0000000000..f16c0c8ec2 --- /dev/null +++ b/docs/images/icons/icon-7-todo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/icons/icon-7.svg b/docs/images/icons/icon-7.svg new file mode 100644 index 0000000000..bfbb8a07c2 --- /dev/null +++ b/docs/images/icons/icon-7.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/images/serialization.svg b/docs/images/serialization.svg new file mode 100644 index 0000000000..af03236c64 --- /dev/null +++ b/docs/images/serialization.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/docs/project.ihp b/docs/project.ihp new file mode 100644 index 0000000000..5f128b0a42 --- /dev/null +++ b/docs/project.ihp @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/docs/serialization.tree b/docs/serialization.tree new file mode 100644 index 0000000000..aac5f8f87a --- /dev/null +++ b/docs/serialization.tree @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/basic-serialization.md b/docs/topics/basic-serialization.md similarity index 93% rename from docs/basic-serialization.md rename to docs/topics/basic-serialization.md index 96e709817d..935ffe987b 100644 --- a/docs/basic-serialization.md +++ b/docs/topics/basic-serialization.md @@ -73,7 +73,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-basic-01.kt). +> You can get the full code [here](../../guide/example/example-basic-01.kt). When we run this code we get the exception. @@ -98,7 +98,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-basic-02.kt). +> You can get the full code [here](../../guide/example/example-basic-02.kt). The `@Serializable` annotation instructs the Kotlin Serialization plugin to automatically generate and hook up a _serializer_ for this class. Now the output of the example is the corresponding JSON. @@ -134,7 +134,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-basic-03.kt). +> You can get the full code [here](../../guide/example/example-basic-03.kt). Running this code we get back the object. @@ -178,7 +178,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-01.kt). +> You can get the full code [here](../../guide/example/example-classes-01.kt). We can clearly see that only the `name` and `stars` properties are present in the JSON output. @@ -228,7 +228,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-02.kt). +> You can get the full code [here](../../guide/example/example-classes-02.kt). This example produces the expected output. @@ -265,7 +265,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-03.kt). +> You can get the full code [here](../../guide/example/example-classes-03.kt). Running this code produces the exception: @@ -292,7 +292,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-04.kt). +> You can get the full code [here](../../guide/example/example-classes-04.kt). It produces the exception: @@ -317,7 +317,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-05.kt). +> You can get the full code [here](../../guide/example/example-classes-05.kt). It produces the following output with the default value for the `language` property. @@ -350,7 +350,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-06.kt). +> You can get the full code [here](../../guide/example/example-classes-06.kt). Since the `language` property was specified in the input, we don't see the "Computing" string printed in the output. @@ -378,7 +378,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-07.kt). +> You can get the full code [here](../../guide/example/example-classes-07.kt). We get the following exception. @@ -405,7 +405,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-08.kt). +> You can get the full code [here](../../guide/example/example-classes-08.kt). Attempts to explicitly specify its value in the serial format, even if the specified value is equal to the default one, produces the following exception. @@ -434,7 +434,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-09.kt). +> You can get the full code [here](../../guide/example/example-classes-09.kt). It produces the following output, which does not have the `language` property because its value is equal to the default one. @@ -475,7 +475,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-10.kt). +> You can get the full code [here](../../guide/example/example-classes-10.kt). As you can see, `language` property is preserved and `projects` is omitted: @@ -500,7 +500,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-11.kt). +> You can get the full code [here](../../guide/example/example-classes-11.kt). This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded). @@ -527,7 +527,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-12.kt). +> You can get the full code [here](../../guide/example/example-classes-12.kt). Even though the `language` property has a default value, it is still an error to attempt to assign the `null` value to it. @@ -561,7 +561,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-13.kt). +> You can get the full code [here](../../guide/example/example-classes-13.kt). When encoded to JSON it results in a nested JSON object. @@ -594,7 +594,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-14.kt). +> You can get the full code [here](../../guide/example/example-classes-14.kt). We simply get the `owner` value encoded twice. @@ -638,7 +638,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-15.kt). +> You can get the full code [here](../../guide/example/example-classes-15.kt). The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`. @@ -667,7 +667,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-classes-16.kt). +> You can get the full code [here](../../guide/example/example-classes-16.kt). Now we see that an abbreviated name `lang` is used in the JSON output. diff --git a/docs/building.md b/docs/topics/building.md similarity index 100% rename from docs/building.md rename to docs/topics/building.md diff --git a/docs/builtin-classes.md b/docs/topics/builtin-classes.md similarity index 92% rename from docs/builtin-classes.md rename to docs/topics/builtin-classes.md index 20bc9aa239..00d27882fa 100644 --- a/docs/builtin-classes.md +++ b/docs/topics/builtin-classes.md @@ -60,7 +60,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-01.kt). +> You can get the full code [here](../../guide/example/example-builtin-01.kt). Their natural representation in JSON is used. @@ -85,7 +85,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-02.kt). +> You can get the full code [here](../../guide/example/example-builtin-02.kt). By default they are serialized to JSON as numbers. @@ -127,7 +127,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-03.kt). +> You can get the full code [here](../../guide/example/example-builtin-03.kt). This JSON gets parsed natively by JavaScript without loss of precision. @@ -158,7 +158,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-04.kt). +> You can get the full code [here](../../guide/example/example-builtin-04.kt). In JSON an enum gets encoded as a string. @@ -189,7 +189,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-05.kt). +> You can get the full code [here](../../guide/example/example-builtin-05.kt). We see that the specified serial name is now used in the resulting JSON. @@ -217,7 +217,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-06.kt). +> You can get the full code [here](../../guide/example/example-builtin-06.kt). ```text {"first":1,"second":{"name":"kotlinx.serialization"}} @@ -245,7 +245,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-07.kt). +> You can get the full code [here](../../guide/example/example-builtin-07.kt). The result is represented as a list in JSON. @@ -272,7 +272,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-08.kt). +> You can get the full code [here](../../guide/example/example-builtin-08.kt). [Set] is also represented as a list in JSON, like all other collections. @@ -307,7 +307,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-09.kt). +> You can get the full code [here](../../guide/example/example-builtin-09.kt). Because the `data.b` property is a [Set], the duplicate values from it disappeared. @@ -334,7 +334,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-10.kt). +> You can get the full code [here](../../guide/example/example-builtin-10.kt). Kotlin maps in JSON are represented as objects. In JSON object keys are always strings, so keys are encoded as strings even if they are numbers in Kotlin, as we can see below. @@ -370,7 +370,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-builtin-11.kt). +> You can get the full code [here](../../guide/example/example-builtin-11.kt). While it may seem useless at first glance, this comes in handy for sealed class serialization, which is explained in the [Polymorphism. Objects](polymorphism.md#objects) section. @@ -399,7 +399,7 @@ fun main() { println(Json.encodeToString(duration)) } ``` -> You can get the full code [here](../guide/example/example-builtin-12.kt). +> You can get the full code [here](../../guide/example/example-builtin-12.kt). Duration is serialized as a string in the ISO-8601-2 format. ```text @@ -425,7 +425,7 @@ fun main() { println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42))) } ``` -> You can get the full code [here](../guide/example/example-builtin-13.kt). +> You can get the full code [here](../../guide/example/example-builtin-13.kt). When encoding, the serializer for `Nothing` was not used diff --git a/docs/compatibility.md b/docs/topics/compatibility.md similarity index 100% rename from docs/compatibility.md rename to docs/topics/compatibility.md diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md new file mode 100644 index 0000000000..953ebdb166 --- /dev/null +++ b/docs/topics/create-custom-serializers.md @@ -0,0 +1,25 @@ +[//]: # (title: Create custom serializers) + +A plugin-generated serializer is convenient, but it may not produce the JSON we want for such a class as `Color`. +Let's study the alternatives. + +## Primitive serializer + +We want to serialize the `Color` class as a hex string with the green color represented as `"00ff00"`. +To achieve this, we write an object that implements the `KSerializer` interface for the `Color` class. + +```kotlin +object ColorAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Color) { + val string = value.rgb.toString(16).padStart(6, '0') + encoder.encodeString(string) + } + + override fun deserialize(decoder: Decoder): Color { + val string = decoder.decodeString() + return Color(string.toInt(16)) + } +} +``` \ No newline at end of file diff --git a/docs/formats.md b/docs/topics/formats.md similarity index 97% rename from docs/formats.md rename to docs/topics/formats.md index 8fb6eaef59..ca5fba0e4d 100644 --- a/docs/formats.md +++ b/docs/topics/formats.md @@ -70,7 +70,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-01.kt). +> You can get the full code [here](../../guide/example/example-formats-01.kt). We print a filtered ASCII representation of the output, writing non-ASCII data in hex, so we see how all the original strings are directly represented in CBOR, but the format delimiters themselves are binary. @@ -125,7 +125,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-02.kt). +> You can get the full code [here](../../guide/example/example-formats-02.kt). It decodes the object, despite the fact that `Project` is missing the `language` property. @@ -192,7 +192,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-03.kt). +> You can get the full code [here](../../guide/example/example-formats-03.kt). As we see, the CBOR byte that precedes the data is different for different types of encoding. @@ -257,7 +257,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-04.kt). +> You can get the full code [here](../../guide/example/example-formats-04.kt). ```text {0A}{15}kotlinx.serialization{12}{06}Kotlin @@ -307,7 +307,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-05.kt). +> You can get the full code [here](../../guide/example/example-formats-05.kt). We see in the output that the number for the first property `name` did not change (as it is numbered from one by default), but it did change for the `language` property. @@ -358,7 +358,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-06.kt). +> You can get the full code [here](../../guide/example/example-formats-06.kt). * The [default][ProtoIntegerType.DEFAULT] is a varint encoding (`intXX`) that is optimized for small non-negative numbers. The value of `1` is encoded in one byte `01`. @@ -415,7 +415,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-07.kt). +> You can get the full code [here](../../guide/example/example-formats-07.kt). ```text {08}{01}{08}{02}{08}{03} @@ -498,7 +498,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-08.kt). +> You can get the full code [here](../../guide/example/example-formats-08.kt). ```text 0a03546f6d1203313233 @@ -570,7 +570,7 @@ fun main() { println(schemas) } ``` -> You can get the full code [here](../guide/example/example-formats-09.kt). +> You can get the full code [here](../../guide/example/example-formats-09.kt). Which would output as follows. @@ -622,7 +622,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-10.kt). +> You can get the full code [here](../../guide/example/example-formats-10.kt). The resulting map has dot-separated keys representing keys of the nested objects. @@ -702,7 +702,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-11.kt). +> You can get the full code [here](../../guide/example/example-formats-11.kt). As a result, we got all the primitive values in our object graph visited and put into a list in _serial_ order. @@ -804,7 +804,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-12.kt). +> You can get the full code [here](../../guide/example/example-formats-12.kt). Now we can convert a list of primitives back to an object tree. @@ -895,7 +895,7 @@ fun main() { } --> -> You can get the full code [here](../guide/example/example-formats-13.kt). +> You can get the full code [here](../../guide/example/example-formats-13.kt). -> You can get the full code [here](../guide/example/example-poly-16.kt). +> You can get the full code [here](../../guide/example/example-poly-16.kt). + +## Data validation + +Another case where you might want to introduce a primary constructor parameter without a property is when you +want to validate its value before storing it to a property. To make it serializable you shall replace it +with a property in the primary constructor, and move the validation to an `init { ... }` block. + +```kotlin +@Serializable +class Project(val name: String) { + init { + require(name.isNotEmpty()) { "name cannot be empty" } + } +} +``` + +A deserialization process works like a regular constructor in Kotlin and calls all `init` blocks, ensuring that you +cannot get an invalid class as a result of deserialization. Let's try it. + +```kotlin +fun main() { + val data = Json.decodeFromString(""" + {"name":""} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-03.kt). + +Running this code produces the exception: + +```text +Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty +``` + + + +## Optional properties + +An object can be deserialized only when all its properties are present in the input. +For example, run the following code. + +```kotlin +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-04.kt). + +It produces the exception: + +```text +Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $ +``` + + + +This problem can be fixed by adding a default value to the property, which automatically makes it optional +for serialization. + +```kotlin +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-05.kt). + +It produces the following output with the default value for the `language` property. + +```text +Project(name=kotlinx.serialization, language=Kotlin) +``` + + + +## Optional property initializer call + +When an optional property is present in the input, the corresponding initializer for this +property is not even called. This is a feature designed for performance, so be careful not +to rely on side effects in initializers. Consider the example below. + +```kotlin +fun computeLanguage(): String { + println("Computing") + return "Kotlin" +} + +@Serializable +data class Project(val name: String, val language: String = computeLanguage()) + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-06.kt). + +Since the `language` property was specified in the input, we don't see the "Computing" string printed +in the output. + +```text +Project(name=kotlinx.serialization, language=Kotlin) +``` + + + +## Required properties + +A property with a default value can be required in a serial format with the [`@Required`][Required] annotation. +Let us change the previous example by marking the `language` property as `@Required`. + +```kotlin +@Serializable +data class Project(val name: String, @Required val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-07.kt). + +We get the following exception. + +```text +Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $ +``` + + + +## Transient properties + +A property can be excluded from serialization by marking it with the [`@Transient`][Transient] annotation +(don't confuse it with [kotlin.jvm.Transient]). Transient properties must have a default value. + +```kotlin +@Serializable +data class Project(val name: String, @Transient val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-08.kt). + +Attempts to explicitly specify its value in the serial format, even if the specified +value is equal to the default one, produces the following exception. + +```text +Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name +Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys. +``` + + + +> The 'ignoreUnknownKeys' feature is explained in the [Ignoring Unknown Keys section](json.md#ignoring-unknown-keys) section. + +## Defaults are not encoded by default + +Default values are not encoded by default in JSON. This behavior is motivated by the fact that in most real-life scenarios +such configuration reduces visual clutter, and saves the amount of data being serialized. + +```kotlin +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-09.kt). + +It produces the following output, which does not have the `language` property because its value is equal to the default one. + +```text +{"name":"kotlinx.serialization"} +``` + + + +See JSON's [Encoding defaults](json.md#encoding-defaults) section on how this behavior can be configured for JSON. +Additionally, this behavior can be controlled without taking format settings into account. +For that purposes, [EncodeDefault] annotation can be used: + +```kotlin +@Serializable +data class Project( + val name: String, + @EncodeDefault val language: String = "Kotlin" +) +``` + +This annotation instructs the framework to always serialize property, regardless of its value or format settings. +It's also possible to tweak it into the opposite behavior using [EncodeDefault.Mode] parameter: + +```kotlin + +@Serializable +data class User( + val name: String, + @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() +) + +fun main() { + val userA = User("Alice", listOf(Project("kotlinx.serialization"))) + val userB = User("Bob") + println(Json.encodeToString(userA)) + println(Json.encodeToString(userB)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-10.kt). + +As you can see, `language` property is preserved and `projects` is omitted: + +```text +{"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} +{"name":"Bob"} +``` + + + +## Nullable properties + +Nullable properties are natively supported by Kotlin Serialization. + +```kotlin +@Serializable +class Project(val name: String, val renamedTo: String? = null) + +fun main() { + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-11.kt). + +This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded). + +```text +{"name":"kotlinx.serialization"} +``` + + + +## Type safety is enforced + +Kotlin Serialization strongly enforces the type safety of the Kotlin programming language. +In particular, let us try to decode a `null` value from a JSON object into a non-nullable Kotlin property `language`. + +```kotlin +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":null} + """) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-12.kt). + +Even though the `language` property has a default value, it is still an error to attempt to assign +the `null` value to it. + +```text +Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language +Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value. +``` + + + +> It might be desired, when decoding 3rd-party JSONs, to coerce `null` to a default value. +> The corresponding feature is explained in the [Coercing input values](json.md#coercing-input-values) section. + +## Referenced objects + +Serializable classes can reference other classes in their serializable properties. +The referenced classes must be also marked as `@Serializable`. + +```kotlin +@Serializable +class Project(val name: String, val owner: User) + +@Serializable +class User(val name: String) + +fun main() { + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-13.kt). + +When encoded to JSON it results in a nested JSON object. + +```text +{"name":"kotlinx.serialization","owner":{"name":"kotlin"}} +``` + +> References to non-serializable classes can be marked as [Transient properties](#transient-properties), or a +> custom serializer can be provided for them as shown in the [Serializers](serializers.md) chapter. + + + +## No compression of repeated references + +Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction +of arbitrary object graphs with repeated object references. For example, let us try to serialize an object +that references the same `owner` instance twice. + +```kotlin +@Serializable +class Project(val name: String, val owner: User, val maintainer: User) + +@Serializable +class User(val name: String) + +fun main() { + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner, owner) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-14.kt). + +We simply get the `owner` value encoded twice. + +```text +{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} +``` + +> Attempt to serialize a circular structure will result in stack overflow. +> You can use the [Transient properties](#transient-properties) to exclude some references from serialization. + + + +## Generic classes + +Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at +compile-time. For example, consider a generic serializable class `Box`. + +```kotlin +@Serializable +class Box(val contents: T) +``` + +The `Box` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`. + + + +```kotlin +@Serializable +class Data( + val a: Box, + val b: Box +) + +fun main() { + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-15.kt). + +The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`. + +```text +{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} +``` + + + +If the actual generic type is not serializable a compile-time error will be produced. + +## Serial field names + +The names of the properties used in encoded representation, JSON in our examples, are the same as +their names in the source code by default. The name that is used for serialization is called a _serial name_, and +can be changed using the [`@SerialName`][SerialName] annotation. For example, we can have a `language` property in +the source with an abbreviated serial name. + +```kotlin +@Serializable +class Project(val name: String, @SerialName("lang") val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-classes-16.kt). + +Now we see that an abbreviated name `lang` is used in the JSON output. + +```text +{"name":"kotlinx.serialization","lang":"Kotlin"} +``` diff --git a/docs/topics/serialization-get-started-create.md b/docs/topics/serialization-get-started-create.md new file mode 100644 index 0000000000..e4b55a6123 --- /dev/null +++ b/docs/topics/serialization-get-started-create.md @@ -0,0 +1,170 @@ +[//]: # (title: Add Kotlin serialization plugins and dependencies) + + +

This is the first part of the Getting started with Kotlin serialization tutorial:

+

First step Add Kotlin serialization plugins and dependencies
+ Second step Serialize an object
+ Third step Add dependencies to a Kotlin Notebook
+ Fourth step Share a Kotlin Notebook
+

+
+ +In this section of the tutorial, you will learn how to add the necessary serialization dependencies using IntelliJ IDEA. + +To get started, first download and install the latest version of [IntelliJ IDEA](https://www.jetbrains.com/idea/download/). + +## Create a project + +1. In IntelliJ IDEA, select **File | New | Project**. +2. In the panel on the left, select **New Project**. +3. Name the new project and change its location if necessary. + + > Select the **Create Git repository** checkbox to place the new project under version control. + > You will be able to do it later at any time. + > + {type="tip"} + +4. From the **Language** list, select **Kotlin**. +5. Select the **IntelliJ** build system. +6. From the **JDK list**, select the [JDK](https://www.oracle.com/java/technologies/downloads/) that you want to use in your project. +7. Enable the **Add sample code** option to create a file with a sample `"Hello World!"` application. + + > You can also enable the **Generate code with onboarding tips** option to add some additional useful comments to your sample code. + > + {type="tip"} + +8. Click **Create**. + +## Add plugins and dependencies for Kotlin serialization + +Before starting, you must configure your build script to use Kotlin serialization tools in your project. +The instructions below cover how to apply the Kotlin serialization plugin and add the necessary dependencies using Gradle +(with Kotlin DSL and Groovy DSL) and Maven. + + + + +1. Add the Kotlin serialization Gradle plugin `kotlin("plugin.serialization")` to your `build.gradle.kts` file: + + ```kotlin + plugins { + kotlin("jvm") version "%kotlinVersion%" + kotlin("plugin.serialization") version "%kotlinVersion%" + } + ``` + +2. Add the JSON serialization library dependency `org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%` to your `build.gradle.kts` file: + + ```kotlin + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%") + } + ``` + + + + + +1. Add the Kotlin serialization Gradle plugin `org.jetbrains.kotlin.plugin.serialization` to your `build.gradle` file: + + ```groovy + plugins { + id 'org.jetbrains.kotlin.jvm' version '%kotlinVersion%' + id 'org.jetbrains.kotlin.plugin.serialization' version '%kotlinVersion%' + } + ``` + +2. Add the JSON serialization library dependency `org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%` to your `build.gradle` file: + + ```groovy + dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%' + } + ``` + + + + + +1. Specify the Kotlin and serialization versions by adding the following properties to your `pom.xml` file: + + ```xml + + %kotlinVersion% + %serializationVersion% + + ``` + +2. Add the Kotlin serialization Maven plugin to the `` section of your `pom.xml` file: + + ```xml + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + + + kotlinx-serialization + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + + ``` + +3. Add the JSON serialization library dependency to the `` section of your `pom.xml` file: + + ```xml + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${serialization.version} + + + ``` + + + + + +To set up the Kotlin compiler plugin for Bazel, see the example provided in the [rules_kotlin repository](https://github.com/bazelbuild/rules_kotlin/tree/master/examples/plugin/src/serialization). + + + + + +### Add Kotlin serialization dependencies for multiplatform projects + +You can use Kotlin serialization in multiplatform projects, including Kotlin/JS and Kotlin/Native. +To do so, you must add the JSON serialization library dependency to your common source set: + +```kotlin +commonMain { + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%") + } +} +``` + +### Optimizing Kotlin Serialization for Android with ProGuard and R8 + +THIS SHOULD BE PLACED IN AN ADVANCED SECTION FOR ANDROID \ No newline at end of file diff --git a/docs/topics/serialization-get-started-overview.md b/docs/topics/serialization-get-started-overview.md new file mode 100644 index 0000000000..2a40efd0a6 --- /dev/null +++ b/docs/topics/serialization-get-started-overview.md @@ -0,0 +1,19 @@ +[//]: # (title: Get started with Kotlin serialization) + +Serialization in Kotlin involves converting objects to a format that can be easily stored or transmitted and later reconstructed. + +Follow these steps to get started with Kotlin serialization: + +![First step](icon-1.svg){width=25}{type="joined"} [Create a project for Kotlin serialization](serialization-get-started-create.md) + +![Second step](icon-2.svg){width=25}{type="joined"} [Serialize an object to JSON]() + +![Third step](icon-3.svg){width=25}{type="joined"} [Deserialize a JSON string]() + +![Fourth step](icon-4.svg){width=25}{type="joined"} [Deserialize a JSON string back to an object]() + +## Next step + +Start by setting up an environment. + +[Proceed to the next part]() \ No newline at end of file diff --git a/docs/topics/serialization-get-started-serialize.md b/docs/topics/serialization-get-started-serialize.md new file mode 100644 index 0000000000..0276e40166 --- /dev/null +++ b/docs/topics/serialization-get-started-serialize.md @@ -0,0 +1,49 @@ +[//]: # (title: Serialize an object) + + +

This is the first part of the Getting started with Kotlin serialization tutorial:

+

First step Add Kotlin serialization plugins and dependencies
+ Second step Serialize an object
+ Third step Add dependencies to a Kotlin Notebook
+ Fourth step Share a Kotlin Notebook
+

+
+ +1. + +2. Make a class serializable by annotating it with `@Serializable`. + + ```kotlin + import kotlinx.serialization.Serializable + + @Serializable + data class Data(val a: Int, val b: String) + ``` + +3. Serialize an instance of this class by calling `Json.encodeToString()`. + + ```kotlin + import kotlinx.serialization.Serializable + import kotlinx.serialization.json.Json + import kotlinx.serialization.encodeToString + + @Serializable + data class Data(val a: Int, val b: String) + + fun main() { + val json = Json.encodeToString(Data(42, "str")) + } + ``` + + As a result, you get a string containing the state of this object in the JSON format: `{"a": 42, "b": "str"}` + + > You can also serialize object collections, such as lists, in a single call: + > + > ```kotlin + > val dataList = listOf(Data(42, "str"), Data(12, "test")) + > val jsonList = Json.encodeToString(dataList) + > ``` + > + {type="note"} + +4. \ No newline at end of file diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md new file mode 100644 index 0000000000..4b7c446475 --- /dev/null +++ b/docs/topics/serialization-get-started.md @@ -0,0 +1,272 @@ +[//]: # (title: Get started with Kotlin serialization) + +[Serialization](serialization.md) in Kotlin involves converting objects to a format that can be easily stored or transmitted and later reconstructed. + +In this guide, we will walk you through the process of configuring your project for Kotlin serialization, +as well as how to serialize and deserialize objects to and from JSON format. + +## Add plugins and dependencies for Kotlin serialization + +Before starting, you must configure your build script to use Kotlin serialization tools in your project. +This guide covers how to apply the Kotlin serialization plugin and add the necessary dependencies using Gradle +(with Kotlin DSL and Groovy DSL) and Maven. + + + + +1. Add the Kotlin serialization Gradle plugin `kotlin("plugin.serialization")` to your `build.gradle.kts` file: + + ```kotlin + plugins { + kotlin("jvm") version "%kotlinVersion%" + kotlin("plugin.serialization") version "%kotlinVersion%" + } + ``` + +2. Add the JSON serialization library dependency `org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%` to your `build.gradle.kts` file: + + ```kotlin + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%") + } + ``` + + + + + +1. Add the Kotlin serialization Gradle plugin `org.jetbrains.kotlin.plugin.serialization` to your `build.gradle` file: + + ```groovy + plugins { + id 'org.jetbrains.kotlin.jvm' version '%kotlinVersion%' + id 'org.jetbrains.kotlin.plugin.serialization' version '%kotlinVersion%' + } + ``` + +2. Add the JSON serialization library dependency `org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%` to your `build.gradle` file: + + ```groovy + dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%' + } + ``` + + + + + +1. Specify the Kotlin and serialization versions by adding the following properties to your `pom.xml` file: + + ```xml + + %kotlinVersion% + %serializationVersion% + + ``` + +2. Add the Kotlin serialization Maven plugin to the `` section of your `pom.xml` file: + + ```xml + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + + + kotlinx-serialization + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + + ``` + +3. Add the JSON serialization library dependency to the `` section of your `pom.xml` file: + + ```xml + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${serialization.version} + + + ``` + + + + + +To set up the Kotlin compiler plugin for Bazel, see the example provided in the [rules_kotlin repository](https://github.com/bazelbuild/rules_kotlin/tree/master/examples/plugin/src/serialization). + + + + + +### Add Kotlin serialization dependencies for multiplatform projects + +You can use Kotlin serialization in multiplatform projects, including Kotlin/JS and Kotlin/Native. +To do so, you must add the JSON serialization library dependency to your common source set: + +```kotlin +commonMain { + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%") + } +} +``` + +## Serialize an object to JSON + +Serialization is the process of converting an object into a format that can be easily stored or transmitted, such as JSON. +In Kotlin, you can serialize objects to JSON using the `kotlinx.serialization` library. + +To make a class serializable, you need to use the `@Serializable` annotation. +This annotation indicates to the compiler to generate the necessary code for serializing and deserializing instances of the class. +For more information, see [The @Serialization annotation](serialization.md#the-serializable-annotation) section. + +When marking a class with the `@Serializable` annotation, consider the following: + +* Only properties that store their values directly, known as backing fields are serialized. Properties that compute their values dynamically using custom getters are not serialized. +* All parameters in the primary constructor must be properties of the object. +* If data validation is needed before serialization, you can use the init block to validate the properties. + +Let's look at an example: + +1. Import the necessary serialization libraries: + + ```kotlin + import kotlinx.serialization.Serializable + import kotlinx.serialization.json.Json + import kotlinx.serialization.encodeToString + ``` + +2. Make a class serializable by annotating it with `@Serializable`. + + ```kotlin + @Serializable + data class Data(val a: Int, val b: String) + ``` + + > The `@Serialization` annotation enables default serialization of all properties in the primary constructor, + > but you can adjust this behavior using various techniques. These include serialization of backing fields, defining + > custom constructors, specifying optional properties, marking properties as required and more. + > These techniques allow for precise control over which properties are serialized and how the serialization process is managed. + > For more information, see [Serialization customization options](serialization-customization-options.md). + > + {type="note"} + +3. Serialize an instance of this class by calling `Json.encodeToString()`. + + ```kotlin + // Imports the necessary libraries + import kotlinx.serialization.Serializable + import kotlinx.serialization.json.Json + import kotlinx.serialization.encodeToString + + @Serializable + data class Data(val a: Int, val b: String) + + fun main() { + val json = Json.encodeToString(Data(42, "str")) + } + ``` + + As a result, you get a string containing the state of this object in the JSON format: `{"a": 42, "b": "str"}` + + > You can serialize object collections, such as lists, in a single call: + > + > ```kotlin + > val dataList = listOf(Data(42, "str"), Data(12, "test")) + > val jsonList = Json.encodeToString(dataList) + > ``` + > + {type="note"} + +4. You can also use the `.serializer()` function to retrieve and use the automatically generated serializer: + + ```kotlin + // Imports the necessary libraries for serialization + import kotlinx.serialization.Serializable + import kotlinx.serialization.json.Json + import kotlinx.serialization.encodeToString + // Imports the KSerializer interface + import kotlinx.serialization.KSerializer + + // Marks the Data class as serializable + @Serializable + data class Data(val a: Int, val b: String) + + fun main() { + // Retrieves the automatically generated serializer for the Data class + val serializer: KSerializer = Data.serializer() + + // Serializes an instance of Data using the retrieved serializer + val json = Json.encodeToString(serializer, Data(42, "str")) + println(json) + // {"a":42,"b":"str"} + } + ``` + The `.serializer()` function allows you to explicitly obtain the serializer instance created by the `kotlinx.serialization` library for your `@Serializable` class, + allowing you to interact with the serialization process directly. + For more information, see the [Create custom serializers](create-custom-serializers.md) page. + +## Deserialize an object from JSON + +Deserialization reconstructs an object from its serialized form. + +To deserialize an object from JSON in Kotlin: + +1. Import the necessary serialization libraries: + + ```kotlin + import kotlinx.serialization.Serializable + import kotlinx.serialization.json.Json + import kotlinx.serialization.decodeFromString + ``` + +2. Make a class serializable by annotating it with `@Serializable`: + + ```kotlin + @Serializable + data class Data(val a: Int, val b: String) + ``` + +3. Use the `Json.decodeFromString()` function to deserialize an object from JSON: + + ```kotlin + import kotlinx.serialization.Serializable + import kotlinx.serialization.json.Json + import kotlinx.serialization.decodeFromString + + @Serializable + data class Data(val a: Int, val b: String) + + fun main() { + val obj = Json.decodeFromString("""{"a":42, "b": "str"}""") + } + ``` + +## What's next? + +* Learn how to create custom serializers in [Create custom serializers](create-custom-serializers.md). +* Discover various techniques for adjusting serialization behavior in [Serialization customization options](serialization-customization-options.md). \ No newline at end of file diff --git a/docs/serialization-guide.md b/docs/topics/serialization-guide.md similarity index 99% rename from docs/serialization-guide.md rename to docs/topics/serialization-guide.md index 65ff69c269..ff92bd44bc 100644 --- a/docs/serialization-guide.md +++ b/docs/topics/serialization-guide.md @@ -6,7 +6,7 @@ Kotlin Serialization fully supports and enforces the Kotlin type system, making objects can be deserialized. Kotlin Serialization is not just a library. It is a compiler plugin that is bundled with the Kotlin -compiler distribution itself. Build configuration is explained in [README.md](../README.md#setup). +compiler distribution itself. Build configuration is explained in [README.md](../../README.md#setup). Once the project is set up, we can start serializing some classes. ## Table of contents diff --git a/docs/topics/serialization.md b/docs/topics/serialization.md new file mode 100644 index 0000000000..655e3ae6a9 --- /dev/null +++ b/docs/topics/serialization.md @@ -0,0 +1,110 @@ +[//]: # (title: Serialization) + +**Serialization** is the process of converting data used by an application to a format that can be transferred over a +network or stored in a database or a file. In turn, deserialization is the opposite process of reading data from an +external source and converting it into a runtime object. +Together, they are essential to most applications that exchange data with third parties. + +Some data serialization formats, such as [JSON](https://www.json.org/json-en.html) and [protocol buffers](https://protobuf.dev/) are particularly common. +Being language-neutral and platform-neutral, they enable data exchange between systems written in any modern language. + +To convert an object tree to a string or to a sequence of bytes, it must go through two mutually intertwined processes: + +1. Serialization: Objects are transformed into a sequence of their primitive values. +This universal process varies depending on the object and is managed by a serializer. +2. Encoding: The primitive sequence is converted into the desired output format, controlled by an encoder. + +![Serialization flow](serialization.svg){width=700} + +The reverse process involves parsing the input format, decoding the primitive values, and then deserializing the resulting +stream into objects. + +In Kotlin, data serialization tools are available in a separate component, kotlinx.serialization. +It consists of several parts: the `org.jetbrains.kotlin.plugin.serialization` Gradle plugin, runtime libraries, and compiler +plugins. + +Compiler plugins, `kotlinx-serialization-compiler-plugin` and `kotlinx-serialization-compiler-plugin-embeddable`, +are published directly to Maven Central. The second plugin is designed for working with the `kotlin-compiler-embeddable` +artifact, which is the default option for scripting artifacts. +Gradle adds compiler plugins to your projects as compiler arguments. + +If you're new to serialization in Kotlin, we recommend starting with the [Get Started with Serialization](serialization-get-started.md) guide. +This section provides a step-by-step guide to help you set up and use Kotlin serialization in your projects. +By following these steps, you can quickly get up to speed with the basics before diving into more complex topics. + +## Libraries + +`kotlinx.serialization` provides sets of libraries for all supported platforms: JVM, JavaScript, Native, and for various +serialization formats, such as JSON, CBOR, and protocol buffers. For the complete list of supported serialization, +see the [supported formats](#supported-formats) section. + +All Kotlin serialization libraries belong to the `org.jetbrains.kotlinx:` group. +Their names start with `kotlinx-serialization-` and have suffixes that reflect the serialization format. +For example: + +* `org.jetbrains.kotlinx:kotlinx-serialization-json` provides JSON serialization for Kotlin projects. +* `org.jetbrains.kotlinx:kotlinx-serialization-cbor` provides CBOR serialization. + +Platform-specific artifacts are handled automatically; you don't need to add them manually. +Use the same dependencies in JVM, JS, Native, and multiplatform projects. + +Note that the `kotlinx.serialization` libraries use their own versioning structure, which doesn't match Kotlin's versioning. +Check out the releases on [GitHub](https://github.com/Kotlin/kotlinx.serialization/releases) to find the latest versions. + +## The @Serializable annotation + +[TBD] + +## Supported formats + +`kotlinx.serialization` includes libraries for various serialization formats: + +* [JSON](https://www.json.org/json-en.html): [`kotlinx-serialization-json`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#json) +* [Protocol buffers](https://protobuf.dev/): [`kotlinx-serialization-protobuf`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#protobuf) +* [CBOR](https://cbor.io/): [`kotlinx-serialization-cbor`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#cbor) +* [Properties](https://en.wikipedia.org/wiki/.properties): [`kotlinx-serialization-properties`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#properties) +* [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md): [`kotlinx-serialization-hocon`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#hocon) (only on JVM) + +Note that all libraries except JSON serialization (`kotlinx-serialization-json`) are [Experimental](https://kotlinlang.org/docs/components-stability.html), which means their API can be changed without notice. + +There are also community-maintained libraries that support more serialization formats, such as [YAML](https://yaml.org/) or [Apache Avro](https://avro.apache.org/). + +For more details about the available serialization formats, see [Serialization formats](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md). + +## Supported types + +Kotlin serialization supports the following built-in primitive types: + +* `Boolean` +* `Byte` +* `Short` +* `Int` +* `Long` +* `Float` +* `Double` +* `Char` +* `String` +* `enums` + +In addition to primitives Kotlin serialization also supports a number of composite types: + +* Pair and Triple: The simple data classes [`Pair`]((https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/)) and [`Triple`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/) from the Kotlin standard library are serializable. +* Lists, Sets, and other collections: The [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/), [`Set`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/), and other [collection](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/) types can be serialized. +These types are represented as lists in formats like JSON. A JSON list can be deserialized into both a `List` and a `Set`, with the `Set` automatically removing duplicate values. +* Maps: A [`Map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/) with primitive or `enum` keys and arbitrary serializable values can be serialized. In JSON, object keys are always strings, so keys are encoded as strings even if they are numbers in Kotlin. Composite keys are not supported by JSON but can be used in other formats. +* Unit and singleton objects: The built-in [`Unit`]() type and singleton objects are serializable. Singletons are serialized as empty structures. +* Duration: Since Kotlin 1.7.20, the [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) class of the `kotlin.time` package is serializable. It is serialized as a string in the ISO-8601-2 format. For example, "PT16M40S" is 16 minutes and 40 seconds. +* Nothing: The [`Nothing`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html) type is also serializable. However, since there are no instances of this class, it is impossible to encode or decode its values. This serializer is used when syntactically some type is needed, but it is not actually used in serialization. + +> Not all classes from the Kotlin standard library are serializable. In particular, ranges and the [`Regex`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/) class are not serializable at the moment. +> Support for their serialization may be added in the future. +> +{type="note"} + +If you would like to see code examples for the supported types, see the GitHub repository with [Built-in types examples](https://github.com/Kotlin/kotlinx.serialization/tree/master/guide/example). + +## What's next + +* Learn the basics of Kotlin serialization in the [Get started with serialization guide](serialization-get-started-overview.md). +* To explore more complex scenarios of JSON serialization, see Advanced JSON Serialization Techniques. +* Delve into Custom JSON Configuration to learn how to create and configure custom formats, write your own serializers, and optimize performance for specific use cases. diff --git a/docs/serializers.md b/docs/topics/serializers.md similarity index 96% rename from docs/serializers.md rename to docs/topics/serializers.md index b1117273e7..b86513e27d 100644 --- a/docs/serializers.md +++ b/docs/topics/serializers.md @@ -62,7 +62,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-01.kt). +> You can get the full code [here](../../guide/example/example-serializer-01.kt). By default this class serializes its `rgb` property into JSON. @@ -96,7 +96,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-02.kt). +> You can get the full code [here](../../guide/example/example-serializer-02.kt). ```text Color(rgb: kotlin.Int) @@ -135,7 +135,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-03.kt). +> You can get the full code [here](../../guide/example/example-serializer-03.kt). As we can see, a serializer was instantiated to serialize a concrete `Box`. @@ -162,7 +162,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-04.kt). +> You can get the full code [here](../../guide/example/example-serializer-04.kt). -> You can get the full code [here](../guide/example/example-serializer-11.kt). +> You can get the full code [here](../../guide/example/example-serializer-11.kt). ```text {"r":0,"g":255,"b":0} @@ -602,7 +602,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-12.kt). +> You can get the full code [here](../../guide/example/example-serializer-12.kt). As before, we got the `Color` class represented as a JSON object with three keys: @@ -676,7 +676,7 @@ fun main() { } --> -> You can get the full code [here](../guide/example/example-serializer-13.kt). +> You can get the full code [here](../../guide/example/example-serializer-13.kt). + +* [Serializable value classes](#serializable-value-classes) +* [Unsigned types support (JSON only)](#unsigned-types-support-json-only) +* [Using value classes in your custom serializers](#using-value-classes-in-your-custom-serializers) + + + +## Serializable value classes + +We can mark value class as serializable: + +```kotlin +@Serializable +@JvmInline +value class Color(val rgb: Int) +``` + +Value class in Kotlin is stored as its underlying type when possible (i.e. no boxing is required). +Serialization framework does not impose any additional restrictions and uses the underlying type where possible as well. + +```kotlin +@Serializable +data class NamedColor(val color: Color, val name: String) + +fun main() { + println(Json.encodeToString(NamedColor(Color(0), "black"))) +} +``` + +In this example, `NamedColor` is serialized as two primitives: `color: Int` and `name: String` without an allocation +of `Color` class. When we run the example, encoding data with JSON format, we get the following +output: + +```text +{"color": 0, "name": "black"} +``` + +As we see, `Color` class is not included during the encoding, only its underlying data. This invariant holds even if the actual value class +is [allocated](https://kotlinlang.org/docs/reference/inline-classes.html#representation) — for example, when value +class is used as a generic type argument: + +```kotlin +@Serializable +class Palette(val colors: List) + +fun main() { + println(Json.encodeToString(Palette(listOf(Color(0), Color(255), Color(128))))) +} +``` + +The snippet produces the following output: + +```text +{"colors":[0, 255, 128]} +``` + +## Unsigned types support (JSON only) + +Kotlin standard library provides ready-to-use unsigned arithmetics, leveraging value classes +to represent unsigned types: `UByte`, `UShort`, `UInt` and `ULong`. +[Json] format has built-in support for them: these types are serialized as theirs string +representations in unsigned form. +These types are handled as regular serializable types by the compiler plugin and can be freely used in serializable classes: + +```kotlin +@Serializable +class Counter(val counted: UByte, val description: String) + +fun main() { + val counted = 239.toUByte() + println(Json.encodeToString(Counter(counted, "tries"))) +} +``` + +The output is following: + +```text +{"counted":239,"description":"tries"} +``` + +> Unsigned types are currently supported only in JSON format. Other formats such as ProtoBuf and CBOR +> use built-in serializers that use an underlying signed representation for unsigned types. + +## Using value classes in your custom serializers + +Let's return to our `NamedColor` example and try to write a custom serializer for it. Normally, as shown +in [Hand-written composite serializer](serializers.md#hand-written-composite-serializer), we would write the following code +in `serialize` method: + +```kotlin +override fun serialize(encoder: Encoder, value: NamedColor) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, Color.serializer(), value.color) + encodeStringElement(descriptor, 1, value.name) + } +} +``` + +However, since `Color` is used as a type argument in [encodeSerializableElement][CompositeEncoder.encodeSerializableElement] function, `value.color` will be boxed +to `Color` wrapper before passing it to the function, preventing the value class optimization. To avoid this, we can use +special [encodeInlineElement][CompositeEncoder.encodeInlineElement] function instead. It uses [serial descriptor][SerialDescriptor] of `Color` ([retrieved][SerialDescriptor.getElementDescriptor] from serial descriptor of `NamedColor`) instead of [KSerializer], +does not have type parameters and does not accept any values. Instead, it returns [Encoder]. Using it, we can encode +unboxed value: + +```kotlin +override fun serialize(encoder: Encoder, value: NamedColor) { + encoder.encodeStructure(descriptor) { + encodeInlineElement(descriptor, 0).encodeInt(value.color) + encodeStringElement(descriptor, 1, value.name) + } +} +``` + +The same principle goes also with [CompositeDecoder]: it has [decodeInlineElement][CompositeDecoder.decodeInlineElement] function that returns [Decoder]. + +If your class should be represented as a primitive (as shown in [Primitive serializer](serializers.md#primitive-serializer) section), +and you cannot use [encodeStructure][Encoder.encodeStructure] function, there is a complementary function in [Encoder] called [encodeInline][Encoder.encodeInline]. +We will use it to show an example how one can represent a class as an unsigned integer. + +Let's start with a UID class: + +```kotlin +@Serializable(UIDSerializer::class) +class UID(val uid: Int) +``` + +`uid` type is `Int`, but suppose we want it to be an unsigned integer in JSON. We can start writing the +following custom serializer: + +```kotlin +object UIDSerializer: KSerializer { + override val descriptor = UInt.serializer().descriptor +} +``` + +Note that we are using here descriptor from `UInt.serializer()` — it means that the class' representation looks like a +UInt's one. + +Then the `serialize` method: + +```kotlin +override fun serialize(encoder: Encoder, value: UID) { + encoder.encodeInline(descriptor).encodeInt(value.uid) +} +``` + +That's where the magic happens — despite we called a regular [encodeInt][Encoder.encodeInt] with a `uid: Int` argument, the output will contain +an unsigned int because of the special encoder from `encodeInline` function. Since JSON format supports unsigned integers, it +recognizes theirs descriptors when they're passed into `encodeInline` and handles consecutive calls as for unsigned integers. + +The `deserialize` method looks symmetrically: + +```kotlin +override fun deserialize(decoder: Decoder): UID { + return UID(decoder.decodeInline(descriptor).decodeInt()) +} +``` + +> 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). +> However, do not use anything in custom serializers for value classes besides `encodeInline`. As we discussed, calls to value class serializer may be +> optimized and replaced with a `encodeInlineElement` calls. +> `encodeInline` and `encodeInlineElement` calls with the same descriptor are considered equivalent and can be replaced with each other — formats should return the same `Encoder`. +> If you embed custom logic in custom value class serializer, you may get different results depending on whether this serializer was called at all +> (and this, in turn, depends on whether value class was boxed or not). + +--- + + + + +[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html + + + +[CompositeEncoder.encodeSerializableElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-serializable-element.html +[CompositeEncoder.encodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-inline-element.html +[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html +[CompositeDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html +[CompositeDecoder.decodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-inline-element.html +[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html +[Encoder.encodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html +[Encoder.encodeInline]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-inline.html +[Encoder.encodeInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-int.html + + + +[SerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html +[SerialDescriptor.getElementDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/get-element-descriptor.html + + + + +[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html + + diff --git a/docs/v.list b/docs/v.list new file mode 100644 index 0000000000..a30391bf1e --- /dev/null +++ b/docs/v.list @@ -0,0 +1,7 @@ + + + + + + + From 70e5d32555c089caeee9c8763283021871f366c6 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Mon, 22 Jul 2024 11:44:20 +0200 Subject: [PATCH 02/14] proposed structure --- docs/serialization.tree | 8 +++++++ .../alternative-serialization-formats.md | 23 ++++++++++++++++++ docs/topics/configure-json-serialization.md | 3 +++ docs/topics/create-custom-serializers.md | 4 ++-- .../serialization-json-configuration.md | 1 + docs/topics/serialization-json-elements.md | 1 + docs/topics/serialization-polymorphism.md | 24 +++++++++++++++++++ docs/topics/serialization-transform-json.md | 1 + 8 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 docs/topics/alternative-serialization-formats.md create mode 100644 docs/topics/configure-json-serialization.md create mode 100644 docs/topics/serialization-json-configuration.md create mode 100644 docs/topics/serialization-json-elements.md create mode 100644 docs/topics/serialization-polymorphism.md create mode 100644 docs/topics/serialization-transform-json.md diff --git a/docs/serialization.tree b/docs/serialization.tree index aac5f8f87a..5275af714e 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -12,6 +12,14 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/topics/alternative-serialization-formats.md b/docs/topics/alternative-serialization-formats.md new file mode 100644 index 0000000000..ca64127590 --- /dev/null +++ b/docs/topics/alternative-serialization-formats.md @@ -0,0 +1,23 @@ +[//]: # (title: Alternative and custom serialization formats) + +## CBOR + +## ProtoBuf + +## Create custom serialization formats (experimental) + +### Implement a basic encoder + +### Implement a basic decoder + +### Encoder and decoder optimization + +#### Add collection support in encoder + +#### Add collection support in decoder + +#### Handle null values + +### Create an efficient binary format + +### Support format specific types diff --git a/docs/topics/configure-json-serialization.md b/docs/topics/configure-json-serialization.md new file mode 100644 index 0000000000..a29d051440 --- /dev/null +++ b/docs/topics/configure-json-serialization.md @@ -0,0 +1,3 @@ +[//]: # (title: Customizing and tranforming JSON serialization) + +INTRO - TBD \ No newline at end of file diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index 953ebdb166..6cec50b251 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -1,7 +1,7 @@ [//]: # (title: Create custom serializers) -A plugin-generated serializer is convenient, but it may not produce the JSON we want for such a class as `Color`. -Let's study the alternatives. +A plugin-generated serializer is convenient, but it may not produce the JSON we want for classes such as `Color`. +In this section we will cover the alternatives. ## Primitive serializer diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md new file mode 100644 index 0000000000..783e90705f --- /dev/null +++ b/docs/topics/serialization-json-configuration.md @@ -0,0 +1 @@ +[//]: # (title: Customize JSON serialization settings) \ No newline at end of file diff --git a/docs/topics/serialization-json-elements.md b/docs/topics/serialization-json-elements.md new file mode 100644 index 0000000000..f49627b287 --- /dev/null +++ b/docs/topics/serialization-json-elements.md @@ -0,0 +1 @@ +[//]: # (title: Managing JSON elements) \ No newline at end of file diff --git a/docs/topics/serialization-polymorphism.md b/docs/topics/serialization-polymorphism.md new file mode 100644 index 0000000000..9ad43f549b --- /dev/null +++ b/docs/topics/serialization-polymorphism.md @@ -0,0 +1,24 @@ +[//]: # (title: Serialize polymorphic classes) + +Polymorphism allows objects of different types to be treated as objects of a common supertype. +In Kotlin, polymorphism enables you to work with various subclasses through a shared interface or superclass. +This is useful for designing flexible and reusable code, especially when the exact type of objects isn't known at compile time. +In the context of serialization, polymorphism helps manage data structures where the runtime type of data can vary. + +## Closed polymorphism + +In serialization, closed polymorphism ensures that all possible subclasses are known and handled, providing a clear and +safe way to serialize and deserialize polymorphic data structures. + +### Static types + +Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined +by *compile-time* types of objects. Let's examine this aspect in more detail and learn how +to serialize polymorphic data structures, where the type of data is determined at runtime. + +To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project` +has just the `name` property, while its derived `class OwnedProject` adds an `owner` property. +In the below example, we serialize `data` variable with a static type of +`Project` that is initialized with an instance of `OwnedProject` at runtime. + +## Open polymorphism \ No newline at end of file diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md new file mode 100644 index 0000000000..0eaae8a8e5 --- /dev/null +++ b/docs/topics/serialization-transform-json.md @@ -0,0 +1 @@ +[//]: # (title: Transform JSON during serialiazation and deserialization) From 54a7a9fd887d14637e95455bae04cb41ba900183 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Mon, 26 Aug 2024 15:38:12 +0200 Subject: [PATCH 03/14] 3/10 docs are ready for review --- docs/serialization.tree | 2 +- .../serialization-customization-options.md | 555 +++++++----------- docs/topics/serialization-get-started.md | 2 +- docs/value-classes.md | 204 ------- 4 files changed, 217 insertions(+), 546 deletions(-) delete mode 100644 docs/value-classes.md diff --git a/docs/serialization.tree b/docs/serialization.tree index 5275af714e..4ccac354fb 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -10,7 +10,7 @@ - + diff --git a/docs/topics/serialization-customization-options.md b/docs/topics/serialization-customization-options.md index 485e210cef..9a9fe4635b 100644 --- a/docs/topics/serialization-customization-options.md +++ b/docs/topics/serialization-customization-options.md @@ -1,17 +1,20 @@ -[//]: # (title: Serialization customization options) +[//]: # (title: Serializable classes) -By default, the `@Serializable` annotation in Kotlin enables the serialization of all properties in the primary constructor. -You can customize this behavior to fit your specific needs. +The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. +However, you can customize this behavior to fit your specific needs. This section covers how to adjust serialization using various techniques to control which properties are serialized and how the serialization process is managed. -To get started, make sure you have the necessary libraries installed. +To get started, make sure you have the necessary libraries imported. For setup instructions, see the [Get started with Kotlin serialization guide](serialization-get-started.md). -## Backing fields are serialized +## The @Serializable annotation -Only a class's properties with backing fields are serialized, so properties with a getter/setter that don't -have a backing field and delegated properties are not serialized, as the following example shows. +The `@Serializable` annotation in Kotlin triggers the automatic serialization of class properties, +allowing classes to be easily converted to and from formats like JSON. + +In Kotlin, only properties with backing fields are serialized. +This means that properties defined solely by getter/setter methods or delegated properties without backing fields are excluded from serialization: ```kotlin @Serializable @@ -38,487 +41,359 @@ fun main() { } ``` -## Constructor properties requirement - -If we want to define the `Project` class so that it takes a path string, and then -deconstructs it into the corresponding properties, we might be tempted to write something like the code below. +Kotlin Serialization natively supports nullable properties. +Like [other defaults](#set-default-values-for-optional-properties), `null` values are not encoded in JSON: ```kotlin @Serializable -class Project(path: String) { - val owner: String = path.substringBefore('/') - val name: String = path.substringAfter('/') -} -``` - -This class does not compile because the `@Serializable` annotation requires that all parameters of the class's primary -constructor be properties. A simple workaround is to define a private primary constructor with the class's -properties, and turn the constructor we wanted into the secondary one. - -```kotlin -@Serializable -class Project private constructor(val owner: String, val name: String) { - constructor(path: String) : this( - owner = path.substringBefore('/'), - name = path.substringAfter('/') - ) - - val path: String - get() = "$owner/$name" -} -``` - -Serialization works with a private primary constructor, and still serializes only backing fields. +// The 'renamedTo' property is nullable and defaults to null, and it's not encoded +class Project(val name: String, val renamedTo: String? = null) -```kotlin fun main() { - println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization"} } ``` -> You can get the full code [here](../../guide/example/example-classes-02.kt). - -This example produces the expected output. - -```text -{"owner":"kotlin","name":"kotlinx.serialization"} -``` - - - -## Data validation - -Another case where you might want to introduce a primary constructor parameter without a property is when you -want to validate its value before storing it to a property. To make it serializable you shall replace it -with a property in the primary constructor, and move the validation to an `init { ... }` block. +Additionally, the type safety of Kotlin is strongly enforced. +If a `null` value is encountered in a JSON object for a non-nullable Kotlin property, +even if the property has a default value, an exception is raised: ```kotlin @Serializable -class Project(val name: String) { - init { - require(name.isNotEmpty()) { "name cannot be empty" } - } -} -``` - -A deserialization process works like a regular constructor in Kotlin and calls all `init` blocks, ensuring that you -cannot get an invalid class as a result of deserialization. Let's try it. +data class Project(val name: String, val language: String = "Kotlin") -```kotlin fun main() { val data = Json.decodeFromString(""" - {"name":""} + {"name":"kotlinx.serialization","language":null} """) println(data) + // JsonDecodingException } ``` -> You can get the full code [here](../../guide/example/example-classes-03.kt). +> If you need to handle `null` values from third-party JSON, you can [coerce them to a default value](json.md#coercing-input-values). +> +{type="tip"} -Running this code produces the exception: - -```text -Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty -``` - - - -## Optional properties - -An object can be deserialized only when all its properties are present in the input. -For example, run the following code. +When an optional property is present in the input, the initializer for that property is not called. +In the following example, since the `language` property is specified in the input, the `Computing` string is not printed +in the output: ```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) +fun computeLanguage(): String { + println("Computing") + return "Kotlin" } -``` - -> You can get the full code [here](../../guide/example/example-classes-04.kt). - -It produces the exception: -```text -Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $ -``` - - - -This problem can be fixed by adding a default value to the property, which automatically makes it optional -for serialization. - -```kotlin @Serializable -data class Project(val name: String, val language: String = "Kotlin") +// Initializer is skipped if `language` is in input +data class Project(val name: String, val language: String = computeLanguage()) fun main() { val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} + {"name":"kotlinx.serialization","language":"Java"} """) println(data) + // Project(name=kotlinx.serialization, language=Java) } ``` -> You can get the full code [here](../../guide/example/example-classes-05.kt). +> This behavior is intended to improve performance. +> Avoid relying on any side effects in the initializer, as they will be bypassed if the initializer is not called. +> +{type="note"} -It produces the following output with the default value for the `language` property. +### Serialization of class references -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -## Optional property initializer call - -When an optional property is present in the input, the corresponding initializer for this -property is not even called. This is a feature designed for performance, so be careful not -to rely on side effects in initializers. Consider the example below. +Serializable classes can contain references to other classes within their properties. +To ensure proper serialization, the referenced classes must also be annotated with `@Serializable`. +When encoded to JSON, this results in a nested JSON object: ```kotlin -fun computeLanguage(): String { - println("Computing") - return "Kotlin" -} +@Serializable +// The 'owner' property references another serializable class `User` +class Project(val name: String, val owner: User) +// The referenced class must also be annotated with `@Serializable` @Serializable -data class Project(val name: String, val language: String = computeLanguage()) +class User(val name: String) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"}} } ``` -> You can get the full code [here](../../guide/example/example-classes-06.kt). - -Since the `language` property was specified in the input, we don't see the "Computing" string printed -in the output. - -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` +> If you need to reference non-serializable classes, you can mark them as [transient properties](#exclude-properties-with-the-transient-annotation), or +> provide a [custom serializer](serializers.md)for them. +> +{type="tip"} - +### Serialization of repeated object references -## Required properties - -A property with a default value can be required in a serial format with the [`@Required`][Required] annotation. -Let us change the previous example by marking the `language` property as `@Required`. +Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction +of arbitrary object graphs with repeated object references. +For example, when serializing an object that references the same instance twice, it is simply encoded twice: ```kotlin @Serializable -data class Project(val name: String, @Required val language: String = "Kotlin") +class Project(val name: String, val owner: User, val maintainer: User) + +@Serializable +class User(val name: String) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) + val owner = User("kotlin") + // 'owner' is referenced twice + val data = Project("kotlinx.serialization", owner, owner) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} } ``` -> You can get the full code [here](../../guide/example/example-classes-07.kt). - -We get the following exception. - -```text -Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $ -``` +> If you attempt to serialize a circular structure, it will result in stack overflow. +> You can use the [Transient properties](#exclude-properties-with-the-transient-annotation) to exclude some references from serialization. +> +{type="tip"} - +### Generic class serialization -## Transient properties - -A property can be excluded from serialization by marking it with the [`@Transient`][Transient] annotation -(don't confuse it with [kotlin.jvm.Transient]). Transient properties must have a default value. +Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at +compile-time. For example, consider a generic serializable class `Box`: ```kotlin @Serializable -data class Project(val name: String, @Transient val language: String = "Kotlin") +// The `Box` class can be used with built-in types like `Int`, or with user-defined types like `Project`. +class Box(val contents: T) +@Serializable +data class Project(val name: String, val language: String) + +@Serializable +class Data( + val a: Box, + val b: Box +) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) + // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} } ``` -> You can get the full code [here](../../guide/example/example-classes-08.kt). - -Attempts to explicitly specify its value in the serial format, even if the specified -value is equal to the default one, produces the following exception. +The type that is serialized to JSON depends on the actual compile-time type parameter specified for `Box`. -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name -Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys. -``` +If the generic type is not serializable, a compile-time error will occur, preventing the code from compiling. - +## Customize serialization behavior -> The 'ignoreUnknownKeys' feature is explained in the [Ignoring Unknown Keys section](json.md#ignoring-unknown-keys) section. +Kotlin Serialization offers various ways to modify how your classes are serialized, allowing you to tailor the process to your specific needs. +This section covers techniques for customizing property names, managing default values, and more. -## Defaults are not encoded by default +### Customize serial names -Default values are not encoded by default in JSON. This behavior is motivated by the fact that in most real-life scenarios -such configuration reduces visual clutter, and saves the amount of data being serialized. +By default, the property names in the serialization output, such as JSON, match their names in the source code. +These names, known as _serial names_, can be customized +using the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation: +For example, you can customize a property’s serial name to be shorter or more descriptive in the serialized output: ```kotlin @Serializable -data class Project(val name: String, val language: String = "Kotlin") +// The language property is abbreviated to lang using @SerialName +class Project(val name: String, @SerialName("lang") val language: String) fun main() { - val data = Project("kotlinx.serialization") + val data = Project("kotlinx.serialization", "Kotlin") + // In the JSON output, the abbreviated name lang is used instead of the full property name println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","lang":"Kotlin"} } ``` -> You can get the full code [here](../../guide/example/example-classes-09.kt). +### Define constructor properties for serialization -It produces the following output, which does not have the `language` property because its value is equal to the default one. +The `@Serializable` annotation requires all parameters of the class's primary constructor to be properties. -```text -{"name":"kotlinx.serialization"} -``` - - - -See JSON's [Encoding defaults](json.md#encoding-defaults) section on how this behavior can be configured for JSON. -Additionally, this behavior can be controlled without taking format settings into account. -For that purposes, [EncodeDefault] annotation can be used: +As a workaround, you can define a private primary constructor with the class's properties and create a +secondary constructor to handle the path string. +Serialization works with a private primary constructor and still serializes only the backing fields: ```kotlin @Serializable -data class Project( - val name: String, - @EncodeDefault val language: String = "Kotlin" -) -``` - -This annotation instructs the framework to always serialize property, regardless of its value or format settings. -It's also possible to tweak it into the opposite behavior using [EncodeDefault.Mode] parameter: - -```kotlin - -@Serializable -data class User( - val name: String, - @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() -) +class Project private constructor(val owner: String, val name: String) { + // Creates a Project object using a path string + constructor(path: String) : this( + owner = path.substringBefore('/'), + name = path.substringAfter('/') + ) + val path: String + get() = "$owner/$name" +} fun main() { - val userA = User("Alice", listOf(Project("kotlinx.serialization"))) - val userB = User("Bob") - println(Json.encodeToString(userA)) - println(Json.encodeToString(userB)) + println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) + // {"owner":"kotlin","name":"kotlinx.serialization"} } ``` -> You can get the full code [here](../../guide/example/example-classes-10.kt). +### Validate data in primary constructor -As you can see, `language` property is preserved and `projects` is omitted: - -```text -{"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} -{"name":"Bob"} -``` - - - -## Nullable properties - -Nullable properties are natively supported by Kotlin Serialization. +When you need to validate a constructor parameter before storing it in a property, +replace the parameter with a property in the primary constructor and perform validation in an `init { ... }` block. +This ensures the class is serializable and that invalid data cannot be deserialized: ```kotlin @Serializable -class Project(val name: String, val renamedTo: String? = null) +class Project(val name: String) { + // Validates that the name is not empty + init { + require(name.isNotEmpty()) { "name cannot be empty" } + } +} fun main() { - val data = Project("kotlinx.serialization") - println(Json.encodeToString(data)) + val data = Json.decodeFromString(""" + {"name":""} + """) + println(data) + // Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty } ``` -> You can get the full code [here](../../guide/example/example-classes-11.kt). +### Set default values for optional properties -This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded). +In Kotlin, an object can only be deserialized when all its properties are present in the input. +If a property is missing, deserialization will fail. -```text -{"name":"kotlinx.serialization"} -``` - - - -## Type safety is enforced - -Kotlin Serialization strongly enforces the type safety of the Kotlin programming language. -In particular, let us try to decode a `null` value from a JSON object into a non-nullable Kotlin property `language`. +This issue can be resolved by adding a default value to the property, which automatically makes it optional for +serialization: ```kotlin @Serializable +// Sets a default value for the optional `language` property data class Project(val name: String, val language: String = "Kotlin") fun main() { val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":null} + {"name":"kotlinx.serialization"} """) println(data) + // Project(name=kotlinx.serialization, language=Kotlin) } ``` -> You can get the full code [here](../../guide/example/example-classes-12.kt). - -Even though the `language` property has a default value, it is still an error to attempt to assign -the `null` value to it. - -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language -Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value. -``` - - - -> It might be desired, when decoding 3rd-party JSONs, to coerce `null` to a default value. -> The corresponding feature is explained in the [Coercing input values](json.md#coercing-input-values) section. +### Make properties required with the @Required annotation -## Referenced objects - -Serializable classes can reference other classes in their serializable properties. -The referenced classes must be also marked as `@Serializable`. +A property with a default value can be made required in a serialized format with the [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) annotation. +This ensures that the property must be present in the input, even if it has a default value: ```kotlin -@Serializable -class Project(val name: String, val owner: User) +// Imports the necessary libraries +import kotlinx.serialization.Required +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json @Serializable -class User(val name: String) +// Marks the `language` property as required +data class Project(val name: String, @Required val language: String = "Kotlin") fun main() { - val owner = User("kotlin") - val data = Project("kotlinx.serialization", owner) - println(Json.encodeToString(data)) + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) + // MissingFieldException } ``` -> You can get the full code [here](../../guide/example/example-classes-13.kt). +### Exclude properties with the @Transient annotation -When encoded to JSON it results in a nested JSON object. - -```text -{"name":"kotlinx.serialization","owner":{"name":"kotlin"}} -``` +A property can be excluded from serialization by marking it with the [`@Transient`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/) annotation +(not to be confused with [kotlin.jvm.Transient](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-transient/)). +Transient properties must have a default value. -> References to non-serializable classes can be marked as [Transient properties](#transient-properties), or a -> custom serializer can be provided for them as shown in the [Serializers](serializers.md) chapter. - - - -## No compression of repeated references - -Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction -of arbitrary object graphs with repeated object references. For example, let us try to serialize an object -that references the same `owner` instance twice. +Attempts to explicitly specify its value in the serial format, even if the specified +value is equal to the default one, produces a `JsonDecodingException` exception: ```kotlin -@Serializable -class Project(val name: String, val owner: User, val maintainer: User) +// Imports the necessary libraries +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.json.Json @Serializable -class User(val name: String) +// Excludes the `language` property from serialization +data class Project(val name: String, @Transient val language: String = "Kotlin") fun main() { - val owner = User("kotlin") - val data = Project("kotlinx.serialization", owner, owner) - println(Json.encodeToString(data)) + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) + // JsonDecodingException } ``` -> You can get the full code [here](../../guide/example/example-classes-14.kt). - -We simply get the `owner` value encoded twice. - -```text -{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} -``` - -> Attempt to serialize a circular structure will result in stack overflow. -> You can use the [Transient properties](#transient-properties) to exclude some references from serialization. +> You can avoid exceptions from unknown keys in JSON, including those marked with the @Transient annotation, with the [`ignoreUnknownKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html) setting. +> For more information, see the [Ignoring Unknown Keys](json.md#ignoring-unknown-keys) section. +> +{type="tip"} - +### Manage serialization of default properties with @EncodedDefault -## Generic classes - -Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at -compile-time. For example, consider a generic serializable class `Box`. +In JSON, default values are not encoded by default. +This behavior improves efficiency by reducing visual clutter and minimizing the amount of serialized data. +In the example below, the `language` property is omitted from the output because its value is equal to the default one: ```kotlin @Serializable -class Box(val contents: T) -``` - -The `Box` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`. - - - -```kotlin -@Serializable -class Data( - val a: Box, - val b: Box -) +data class Project(val name: String, val language: String = "Kotlin") fun main() { - val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + val data = Project("kotlinx.serialization") println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization"} } ``` -> You can get the full code [here](../../guide/example/example-classes-15.kt). - -The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`. - -```text -{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} -``` - - +> You can learn more about how this behavior can be configured in the JSON format in the [Encoding defaults](json.md#encoding-defaults) section. +> +{type="tip"} -If the actual generic type is not serializable a compile-time error will be produced. - -## Serial field names - -The names of the properties used in encoded representation, JSON in our examples, are the same as -their names in the source code by default. The name that is used for serialization is called a _serial name_, and -can be changed using the [`@SerialName`][SerialName] annotation. For example, we can have a `language` property in -the source with an abbreviated serial name. +To ensure that a property is always serialized, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. +It's also possible to do the opposite by configuring the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. +Let's look at an example, where the `language` property is included in the serialized output regardless of its value, +while the `projects` property is not serialized when it is an empty list: ```kotlin @Serializable -class Project(val name: String, @SerialName("lang") val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) -} -``` +data class Project( + val name: String, + // The 'language' property will always be included in the serialized output, even if it has the default value "Kotlin" + @EncodeDefault val language: String = "Kotlin" +) -> You can get the full code [here](../../guide/example/example-classes-16.kt). +@Serializable +data class User( + val name: String, + // The 'projects' property will never be included in the serialized output, even if it has a value + // Since the default value is an empty list, 'projects' will be omitted unless it contains elements + @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() +) -Now we see that an abbreviated name `lang` is used in the JSON output. +fun main() { + val userA = User("Alice", listOf(Project("kotlinx.serialization"))) + val userB = User("Bob") + // 'projects' is serialized because it contains a value, and 'language' is always serialized + println(Json.encodeToString(userA)) + // {"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} -```text -{"name":"kotlinx.serialization","lang":"Kotlin"} + // 'projects' is omitted because it's an empty list and EncodeDefault.Mode is set to NEVER, so it's not serialized + println(Json.encodeToString(userB)) + // {"name":"Bob"} +} ``` diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md index 4b7c446475..4a585a82ea 100644 --- a/docs/topics/serialization-get-started.md +++ b/docs/topics/serialization-get-started.md @@ -140,7 +140,7 @@ commonMain { Serialization is the process of converting an object into a format that can be easily stored or transmitted, such as JSON. In Kotlin, you can serialize objects to JSON using the `kotlinx.serialization` library. -To make a class serializable, you need to use the `@Serializable` annotation. +To make a class serializable, you need to use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation. This annotation indicates to the compiler to generate the necessary code for serializing and deserializing instances of the class. For more information, see [The @Serialization annotation](serialization.md#the-serializable-annotation) section. diff --git a/docs/value-classes.md b/docs/value-classes.md deleted file mode 100644 index fed90f6a2e..0000000000 --- a/docs/value-classes.md +++ /dev/null @@ -1,204 +0,0 @@ -# Serialization and value classes (IR-specific) - -This appendix describes how value classes are handled by kotlinx.serialization. - -> Features described are available on JVM/IR (enabled by default), JS/IR and Native backends. - -**Table of contents** - - - -* [Serializable value classes](#serializable-value-classes) -* [Unsigned types support (JSON only)](#unsigned-types-support-json-only) -* [Using value classes in your custom serializers](#using-value-classes-in-your-custom-serializers) - - - -## Serializable value classes - -We can mark value class as serializable: - -```kotlin -@Serializable -@JvmInline -value class Color(val rgb: Int) -``` - -Value class in Kotlin is stored as its underlying type when possible (i.e. no boxing is required). -Serialization framework does not impose any additional restrictions and uses the underlying type where possible as well. - -```kotlin -@Serializable -data class NamedColor(val color: Color, val name: String) - -fun main() { - println(Json.encodeToString(NamedColor(Color(0), "black"))) -} -``` - -In this example, `NamedColor` is serialized as two primitives: `color: Int` and `name: String` without an allocation -of `Color` class. When we run the example, encoding data with JSON format, we get the following -output: - -```text -{"color": 0, "name": "black"} -``` - -As we see, `Color` class is not included during the encoding, only its underlying data. This invariant holds even if the actual value class -is [allocated](https://kotlinlang.org/docs/reference/inline-classes.html#representation) — for example, when value -class is used as a generic type argument: - -```kotlin -@Serializable -class Palette(val colors: List) - -fun main() { - println(Json.encodeToString(Palette(listOf(Color(0), Color(255), Color(128))))) -} -``` - -The snippet produces the following output: - -```text -{"colors":[0, 255, 128]} -``` - -## Unsigned types support (JSON only) - -Kotlin standard library provides ready-to-use unsigned arithmetics, leveraging value classes -to represent unsigned types: `UByte`, `UShort`, `UInt` and `ULong`. -[Json] format has built-in support for them: these types are serialized as theirs string -representations in unsigned form. -These types are handled as regular serializable types by the compiler plugin and can be freely used in serializable classes: - -```kotlin -@Serializable -class Counter(val counted: UByte, val description: String) - -fun main() { - val counted = 239.toUByte() - println(Json.encodeToString(Counter(counted, "tries"))) -} -``` - -The output is following: - -```text -{"counted":239,"description":"tries"} -``` - -> Unsigned types are currently supported only in JSON format. Other formats such as ProtoBuf and CBOR -> use built-in serializers that use an underlying signed representation for unsigned types. - -## Using value classes in your custom serializers - -Let's return to our `NamedColor` example and try to write a custom serializer for it. Normally, as shown -in [Hand-written composite serializer](serializers.md#hand-written-composite-serializer), we would write the following code -in `serialize` method: - -```kotlin -override fun serialize(encoder: Encoder, value: NamedColor) { - encoder.encodeStructure(descriptor) { - encodeSerializableElement(descriptor, 0, Color.serializer(), value.color) - encodeStringElement(descriptor, 1, value.name) - } -} -``` - -However, since `Color` is used as a type argument in [encodeSerializableElement][CompositeEncoder.encodeSerializableElement] function, `value.color` will be boxed -to `Color` wrapper before passing it to the function, preventing the value class optimization. To avoid this, we can use -special [encodeInlineElement][CompositeEncoder.encodeInlineElement] function instead. It uses [serial descriptor][SerialDescriptor] of `Color` ([retrieved][SerialDescriptor.getElementDescriptor] from serial descriptor of `NamedColor`) instead of [KSerializer], -does not have type parameters and does not accept any values. Instead, it returns [Encoder]. Using it, we can encode -unboxed value: - -```kotlin -override fun serialize(encoder: Encoder, value: NamedColor) { - encoder.encodeStructure(descriptor) { - encodeInlineElement(descriptor, 0).encodeInt(value.color) - encodeStringElement(descriptor, 1, value.name) - } -} -``` - -The same principle goes also with [CompositeDecoder]: it has [decodeInlineElement][CompositeDecoder.decodeInlineElement] function that returns [Decoder]. - -If your class should be represented as a primitive (as shown in [Primitive serializer](serializers.md#primitive-serializer) section), -and you cannot use [encodeStructure][Encoder.encodeStructure] function, there is a complementary function in [Encoder] called [encodeInline][Encoder.encodeInline]. -We will use it to show an example how one can represent a class as an unsigned integer. - -Let's start with a UID class: - -```kotlin -@Serializable(UIDSerializer::class) -class UID(val uid: Int) -``` - -`uid` type is `Int`, but suppose we want it to be an unsigned integer in JSON. We can start writing the -following custom serializer: - -```kotlin -object UIDSerializer: KSerializer { - override val descriptor = UInt.serializer().descriptor -} -``` - -Note that we are using here descriptor from `UInt.serializer()` — it means that the class' representation looks like a -UInt's one. - -Then the `serialize` method: - -```kotlin -override fun serialize(encoder: Encoder, value: UID) { - encoder.encodeInline(descriptor).encodeInt(value.uid) -} -``` - -That's where the magic happens — despite we called a regular [encodeInt][Encoder.encodeInt] with a `uid: Int` argument, the output will contain -an unsigned int because of the special encoder from `encodeInline` function. Since JSON format supports unsigned integers, it -recognizes theirs descriptors when they're passed into `encodeInline` and handles consecutive calls as for unsigned integers. - -The `deserialize` method looks symmetrically: - -```kotlin -override fun deserialize(decoder: Decoder): UID { - return UID(decoder.decodeInline(descriptor).decodeInt()) -} -``` - -> 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). -> However, do not use anything in custom serializers for value classes besides `encodeInline`. As we discussed, calls to value class serializer may be -> optimized and replaced with a `encodeInlineElement` calls. -> `encodeInline` and `encodeInlineElement` calls with the same descriptor are considered equivalent and can be replaced with each other — formats should return the same `Encoder`. -> If you embed custom logic in custom value class serializer, you may get different results depending on whether this serializer was called at all -> (and this, in turn, depends on whether value class was boxed or not). - ---- - - - - -[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html - - - -[CompositeEncoder.encodeSerializableElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-serializable-element.html -[CompositeEncoder.encodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-inline-element.html -[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html -[CompositeDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html -[CompositeDecoder.decodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-inline-element.html -[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html -[Encoder.encodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html -[Encoder.encodeInline]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-inline.html -[Encoder.encodeInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-int.html - - - -[SerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html -[SerialDescriptor.getElementDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/get-element-descriptor.html - - - - -[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html - - From 42220f020513babf3dba3ca59e63aab87230ce14 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Mon, 2 Sep 2024 16:31:43 +0200 Subject: [PATCH 04/14] continuing restructuring effort 50% done --- docs/guide/example/example-basic-01.kt | 12 + docs/guide/example/example-basic-02.kt | 13 + docs/guide/example/example-basic-03.kt | 15 + docs/guide/example/example-builtin-01.kt | 18 + docs/guide/example/example-classes-01.kt | 129 ++ docs/guide/example/example-classes-02.kt | 20 + docs/guide/example/example-classes-03.kt | 19 + docs/guide/example/example-classes-04.kt | 15 + docs/guide/example/example-classes-05.kt | 15 + docs/guide/example/example-classes-06.kt | 20 + docs/guide/example/example-classes-07.kt | 15 + docs/guide/example/example-classes-08.kt | 15 + docs/guide/example/example-classes-09.kt | 13 + docs/guide/example/example-classes-10.kt | 25 + docs/guide/example/example-classes-11.kt | 13 + docs/guide/example/example-classes-12.kt | 15 + docs/guide/example/example-classes-13.kt | 17 + docs/guide/example/example-classes-14.kt | 17 + docs/guide/example/example-classes-15.kt | 22 + .../guide}/example/example-classes-16.kt | 0 docs/guide/example/example-formats-01.kt | 21 + docs/guide/example/example-json-01.kt | 9 + docs/guide/example/example-json-02.kt | 23 + docs/guide/example/example-json-03.kt | 16 + docs/guide/example/example-json-04.kt | 12 + docs/guide/example/example-json-05.kt | 16 + docs/guide/example/example-json-06.kt | 20 + docs/guide/example/example-json-07.kt | 14 + docs/guide/example/example-json-08.kt | 17 + docs/guide/example/example-json-09.kt | 15 + docs/guide/example/example-json-10.kt | 14 + docs/guide/example/example-json-11.kt | 18 + docs/guide/example/example-json-12.kt | 28 + docs/guide/example/example-json-13.kt | 17 + docs/guide/example/example-json-14.kt | 13 + docs/guide/example/example-json-15.kt | 12 + docs/guide/example/example-json-16.kt | 54 + docs/guide/example/example-poly-01.kt | 15 + docs/guide/example/example-serializer-01.kt | 13 + docs/guide/test/BasicSerializationTest.kt | 144 ++ docs/guide/test/BuiltinClassesTest.kt | 99 ++ docs/guide/test/FormatsTest.kt | 151 ++ docs/guide/test/JsonTest.kt | 70 + docs/guide/test/PolymorphismTest.kt | 154 ++ docs/guide/test/SerializersTest.kt | 170 ++ docs/serialization.tree | 4 +- docs/topics/basic-serialization.md | 36 +- docs/topics/configure-json-serialization.md | 63 +- docs/topics/create-custom-serializers.md | 90 +- docs/topics/json.md | 1469 ----------------- docs/topics/knit.properties | 4 +- .../serialization-customization-options.md | 164 +- docs/topics/serialization-guide.md | 30 - .../serialization-json-configuration.md | 693 +++++++- docs/topics/serialization-json-elements.md | 300 +++- docs/topics/serialization-transform-json.md | 409 ++++- guide/example/example-classes-01.kt | 13 +- guide/example/example-classes-02.kt | 17 +- guide/example/example-classes-03.kt | 11 +- guide/example/example-classes-04.kt | 13 +- guide/example/example-classes-05.kt | 17 +- guide/example/example-classes-06.kt | 19 +- guide/example/example-classes-07.kt | 20 +- guide/example/example-classes-08.kt | 13 +- guide/example/example-classes-09.kt | 16 +- guide/example/example-classes-10.kt | 28 +- guide/example/example-classes-11.kt | 12 +- guide/example/example-classes-12.kt | 13 +- guide/example/example-classes-13.kt | 19 +- guide/example/example-classes-14.kt | 11 +- guide/example/example-classes-15.kt | 30 +- guide/example/example-json-01.kt | 6 +- guide/example/example-json-02.kt | 5 +- guide/example/example-json-03.kt | 5 +- guide/example/example-json-04.kt | 15 +- guide/example/example-json-05.kt | 6 +- guide/example/example-json-06.kt | 10 +- guide/example/example-json-07.kt | 5 +- guide/example/example-json-08.kt | 7 +- guide/example/example-json-09.kt | 5 +- guide/example/example-json-10.kt | 6 +- guide/example/example-json-11.kt | 21 +- guide/example/example-json-12.kt | 10 +- guide/example/example-json-13.kt | 5 +- guide/example/example-json-14.kt | 2 +- guide/example/example-json-15.kt | 2 +- guide/example/example-json-16.kt | 2 +- ...json-17.kt => example-json-elements-01.kt} | 4 +- ...json-18.kt => example-json-elements-02.kt} | 4 +- ...json-19.kt => example-json-elements-03.kt} | 4 +- ...json-20.kt => example-json-elements-04.kt} | 4 +- ...json-21.kt => example-json-elements-05.kt} | 4 +- ...json-22.kt => example-json-elements-06.kt} | 4 +- ...json-23.kt => example-json-elements-07.kt} | 4 +- ...json-24.kt => example-json-elements-08.kt} | 4 +- ...son-25.kt => example-json-transform-01.kt} | 7 +- ...son-26.kt => example-json-transform-02.kt} | 5 +- ...son-27.kt => example-json-transform-03.kt} | 5 +- ...son-28.kt => example-json-transform-04.kt} | 5 +- ...son-29.kt => example-json-transform-05.kt} | 5 +- ...son-30.kt => example-json-transform-06.kt} | 5 +- guide/test/BasicSerializationTest.kt | 77 +- guide/test/JsonTest.kt | 120 +- guide/test/JsonTestElements.kt | 70 + guide/test/JsonTestTransform.kt | 53 + 105 files changed, 3714 insertions(+), 1859 deletions(-) create mode 100644 docs/guide/example/example-basic-01.kt create mode 100644 docs/guide/example/example-basic-02.kt create mode 100644 docs/guide/example/example-basic-03.kt create mode 100644 docs/guide/example/example-builtin-01.kt create mode 100644 docs/guide/example/example-classes-01.kt create mode 100644 docs/guide/example/example-classes-02.kt create mode 100644 docs/guide/example/example-classes-03.kt create mode 100644 docs/guide/example/example-classes-04.kt create mode 100644 docs/guide/example/example-classes-05.kt create mode 100644 docs/guide/example/example-classes-06.kt create mode 100644 docs/guide/example/example-classes-07.kt create mode 100644 docs/guide/example/example-classes-08.kt create mode 100644 docs/guide/example/example-classes-09.kt create mode 100644 docs/guide/example/example-classes-10.kt create mode 100644 docs/guide/example/example-classes-11.kt create mode 100644 docs/guide/example/example-classes-12.kt create mode 100644 docs/guide/example/example-classes-13.kt create mode 100644 docs/guide/example/example-classes-14.kt create mode 100644 docs/guide/example/example-classes-15.kt rename {guide => docs/guide}/example/example-classes-16.kt (100%) create mode 100644 docs/guide/example/example-formats-01.kt create mode 100644 docs/guide/example/example-json-01.kt create mode 100644 docs/guide/example/example-json-02.kt create mode 100644 docs/guide/example/example-json-03.kt create mode 100644 docs/guide/example/example-json-04.kt create mode 100644 docs/guide/example/example-json-05.kt create mode 100644 docs/guide/example/example-json-06.kt create mode 100644 docs/guide/example/example-json-07.kt create mode 100644 docs/guide/example/example-json-08.kt create mode 100644 docs/guide/example/example-json-09.kt create mode 100644 docs/guide/example/example-json-10.kt create mode 100644 docs/guide/example/example-json-11.kt create mode 100644 docs/guide/example/example-json-12.kt create mode 100644 docs/guide/example/example-json-13.kt create mode 100644 docs/guide/example/example-json-14.kt create mode 100644 docs/guide/example/example-json-15.kt create mode 100644 docs/guide/example/example-json-16.kt create mode 100644 docs/guide/example/example-poly-01.kt create mode 100644 docs/guide/example/example-serializer-01.kt create mode 100644 docs/guide/test/BasicSerializationTest.kt create mode 100644 docs/guide/test/BuiltinClassesTest.kt create mode 100644 docs/guide/test/FormatsTest.kt create mode 100644 docs/guide/test/JsonTest.kt create mode 100644 docs/guide/test/PolymorphismTest.kt create mode 100644 docs/guide/test/SerializersTest.kt rename guide/example/{example-json-17.kt => example-json-elements-01.kt} (60%) rename guide/example/{example-json-18.kt => example-json-elements-02.kt} (74%) rename guide/example/{example-json-19.kt => example-json-elements-03.kt} (76%) rename guide/example/{example-json-20.kt => example-json-elements-04.kt} (71%) rename guide/example/{example-json-21.kt => example-json-elements-05.kt} (77%) rename guide/example/{example-json-22.kt => example-json-elements-06.kt} (82%) rename guide/example/{example-json-23.kt => example-json-elements-07.kt} (76%) rename guide/example/{example-json-24.kt => example-json-elements-08.kt} (58%) rename guide/example/{example-json-25.kt => example-json-transform-01.kt} (86%) rename guide/example/{example-json-26.kt => example-json-transform-02.kt} (80%) rename guide/example/{example-json-27.kt => example-json-transform-03.kt} (80%) rename guide/example/{example-json-28.kt => example-json-transform-04.kt} (83%) rename guide/example/{example-json-29.kt => example-json-transform-05.kt} (91%) rename guide/example/{example-json-30.kt => example-json-transform-06.kt} (87%) create mode 100644 guide/test/JsonTestElements.kt create mode 100644 guide/test/JsonTestTransform.kt diff --git a/docs/guide/example/example-basic-01.kt b/docs/guide/example/example-basic-01.kt new file mode 100644 index 0000000000..7d0d17a952 --- /dev/null +++ b/docs/guide/example/example-basic-01.kt @@ -0,0 +1,12 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleBasic01 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +class Project(val name: String, val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-basic-02.kt b/docs/guide/example/example-basic-02.kt new file mode 100644 index 0000000000..3a5ebb9408 --- /dev/null +++ b/docs/guide/example/example-basic-02.kt @@ -0,0 +1,13 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleBasic02 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Project(val name: String, val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-basic-03.kt b/docs/guide/example/example-basic-03.kt new file mode 100644 index 0000000000..f70369e5d4 --- /dev/null +++ b/docs/guide/example/example-basic-03.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleBasic03 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) +} diff --git a/docs/guide/example/example-builtin-01.kt b/docs/guide/example/example-builtin-01.kt new file mode 100644 index 0000000000..ee2044497f --- /dev/null +++ b/docs/guide/example/example-builtin-01.kt @@ -0,0 +1,18 @@ +// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +package example.exampleBuiltin01 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +import kotlin.math.* + +@Serializable +class Data( + val answer: Int, + val pi: Double +) + +fun main() { + val data = Data(42, PI) + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-classes-01.kt b/docs/guide/example/example-classes-01.kt new file mode 100644 index 0000000000..ebb8fc9429 --- /dev/null +++ b/docs/guide/example/example-classes-01.kt @@ -0,0 +1,129 @@ +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. +package example.exampleClasses01 + +@Serializable +class Project( + // name is a property with backing field -- serialized + var name: String +) { + // stars is property with a backing field -- serialized + var stars: Int = 0 + + // path is getter only, no backing field -- not serialized + val path: String + get() = "kotlin/$name" + + // id is a delegated property -- not serialized + var id by ::name +} + +fun main() { + val data = Project("kotlinx.serialization").apply { stars = 9000 } + // Only the name and the stars properties are present in the JSON output. + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","stars":9000} +} + +@Serializable +// The 'renamedTo' property is nullable and defaults to null, and it's not encoded +class Project(val name: String, val renamedTo: String? = null) + +fun main() { + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization"} +} + +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":null} + """) + println(data) + // JsonDecodingException +} + +fun computeLanguage(): String { + println("Computing") + return "Kotlin" +} + +@Serializable +// Initializer is skipped if `language` is in input +data class Project(val name: String, val language: String = computeLanguage()) + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Java"} + """) + println(data) + // Project(name=kotlinx.serialization, language=Java) +} + +@Serializable +// The 'owner' property references another serializable class `User` +class Project(val name: String, val owner: User) + +// The referenced class must also be annotated with `@Serializable` +@Serializable +class User(val name: String) + +fun main() { + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"}} +} + +@Serializable +class Project(val name: String, val owner: User, val maintainer: User) + +@Serializable +class User(val name: String) + +fun main() { + val owner = User("kotlin") + // 'owner' is referenced twice + val data = Project("kotlinx.serialization", owner, owner) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} +} + +@Serializable +// The `Box` class can be used with built-in types like `Int`, or with user-defined types like `Project`. +class Box(val contents: T) +@Serializable +data class Project(val name: String, val language: String) + +@Serializable +class Data( + val a: Box, + val b: Box +) + +fun main() { + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) + // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} +} + +@Serializable +// The language property is abbreviated to lang using @SerialName +class Project(val name: String, @SerialName("lang") val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + // In the JSON output, the abbreviated name lang is used instead of the full property name + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","lang":"Kotlin"} +} + +@Serializable +class Project(val name: String, @SerialName("lang") val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-classes-02.kt b/docs/guide/example/example-classes-02.kt new file mode 100644 index 0000000000..969cba9e5b --- /dev/null +++ b/docs/guide/example/example-classes-02.kt @@ -0,0 +1,20 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses02 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Project private constructor(val owner: String, val name: String) { + constructor(path: String) : this( + owner = path.substringBefore('/'), + name = path.substringAfter('/') + ) + + val path: String + get() = "$owner/$name" +} + +fun main() { + println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) +} diff --git a/docs/guide/example/example-classes-03.kt b/docs/guide/example/example-classes-03.kt new file mode 100644 index 0000000000..cb99131bcb --- /dev/null +++ b/docs/guide/example/example-classes-03.kt @@ -0,0 +1,19 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses03 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Project(val name: String) { + init { + require(name.isNotEmpty()) { "name cannot be empty" } + } +} + +fun main() { + val data = Json.decodeFromString(""" + {"name":""} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-04.kt b/docs/guide/example/example-classes-04.kt new file mode 100644 index 0000000000..5b69111162 --- /dev/null +++ b/docs/guide/example/example-classes-04.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses04 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-05.kt b/docs/guide/example/example-classes-05.kt new file mode 100644 index 0000000000..9143b2613c --- /dev/null +++ b/docs/guide/example/example-classes-05.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses05 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-06.kt b/docs/guide/example/example-classes-06.kt new file mode 100644 index 0000000000..c37b336d11 --- /dev/null +++ b/docs/guide/example/example-classes-06.kt @@ -0,0 +1,20 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses06 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +fun computeLanguage(): String { + println("Computing") + return "Kotlin" +} + +@Serializable +data class Project(val name: String, val language: String = computeLanguage()) + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-07.kt b/docs/guide/example/example-classes-07.kt new file mode 100644 index 0000000000..19aa153b40 --- /dev/null +++ b/docs/guide/example/example-classes-07.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses07 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, @Required val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-08.kt b/docs/guide/example/example-classes-08.kt new file mode 100644 index 0000000000..d1cf638d21 --- /dev/null +++ b/docs/guide/example/example-classes-08.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses08 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, @Transient val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-09.kt b/docs/guide/example/example-classes-09.kt new file mode 100644 index 0000000000..79231f7017 --- /dev/null +++ b/docs/guide/example/example-classes-09.kt @@ -0,0 +1,13 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses09 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-classes-10.kt b/docs/guide/example/example-classes-10.kt new file mode 100644 index 0000000000..c5a1f739ce --- /dev/null +++ b/docs/guide/example/example-classes-10.kt @@ -0,0 +1,25 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses10 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project( + val name: String, + @EncodeDefault val language: String = "Kotlin" +) + + +@Serializable +data class User( + val name: String, + @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() +) + +fun main() { + val userA = User("Alice", listOf(Project("kotlinx.serialization"))) + val userB = User("Bob") + println(Json.encodeToString(userA)) + println(Json.encodeToString(userB)) +} diff --git a/docs/guide/example/example-classes-11.kt b/docs/guide/example/example-classes-11.kt new file mode 100644 index 0000000000..18a921b961 --- /dev/null +++ b/docs/guide/example/example-classes-11.kt @@ -0,0 +1,13 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses11 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Project(val name: String, val renamedTo: String? = null) + +fun main() { + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-classes-12.kt b/docs/guide/example/example-classes-12.kt new file mode 100644 index 0000000000..232ee47523 --- /dev/null +++ b/docs/guide/example/example-classes-12.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses12 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":null} + """) + println(data) +} diff --git a/docs/guide/example/example-classes-13.kt b/docs/guide/example/example-classes-13.kt new file mode 100644 index 0000000000..4b93c0dff3 --- /dev/null +++ b/docs/guide/example/example-classes-13.kt @@ -0,0 +1,17 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses13 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Project(val name: String, val owner: User) + +@Serializable +class User(val name: String) + +fun main() { + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner) + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-classes-14.kt b/docs/guide/example/example-classes-14.kt new file mode 100644 index 0000000000..3f2d7ce086 --- /dev/null +++ b/docs/guide/example/example-classes-14.kt @@ -0,0 +1,17 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses14 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Project(val name: String, val owner: User, val maintainer: User) + +@Serializable +class User(val name: String) + +fun main() { + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner, owner) + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-classes-15.kt b/docs/guide/example/example-classes-15.kt new file mode 100644 index 0000000000..b259e0d7a3 --- /dev/null +++ b/docs/guide/example/example-classes-15.kt @@ -0,0 +1,22 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.exampleClasses15 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Box(val contents: T) + +@Serializable +data class Project(val name: String, val language: String) + +@Serializable +class Data( + val a: Box, + val b: Box +) + +fun main() { + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) +} diff --git a/guide/example/example-classes-16.kt b/docs/guide/example/example-classes-16.kt similarity index 100% rename from guide/example/example-classes-16.kt rename to docs/guide/example/example-classes-16.kt diff --git a/docs/guide/example/example-formats-01.kt b/docs/guide/example/example-formats-01.kt new file mode 100644 index 0000000000..ad2b3e6d74 --- /dev/null +++ b/docs/guide/example/example-formats-01.kt @@ -0,0 +1,21 @@ +// This file was automatically generated from formats.md by Knit tool. Do not edit. +package example.exampleFormats01 + +import kotlinx.serialization.* +import kotlinx.serialization.cbor.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + val bytes = Cbor.encodeToByteArray(data) + println(bytes.toAsciiHexString()) + val obj = Cbor.decodeFromByteArray(bytes) + println(obj) +} diff --git a/docs/guide/example/example-json-01.kt b/docs/guide/example/example-json-01.kt new file mode 100644 index 0000000000..270e154a35 --- /dev/null +++ b/docs/guide/example/example-json-01.kt @@ -0,0 +1,9 @@ +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJson01 + +fun main() { + val element = Json.parseToJsonElement(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(element) +} diff --git a/docs/guide/example/example-json-02.kt b/docs/guide/example/example-json-02.kt new file mode 100644 index 0000000000..aa06c019bb --- /dev/null +++ b/docs/guide/example/example-json-02.kt @@ -0,0 +1,23 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson02 + +val format = Json { isLenient = true } + +enum class Status { SUPPORTED } + +@Serializable +data class Project(val name: String, val status: Status, val votes: Int) + +fun main() { + // Decodes a JSON string with lenient parsing + // Lenient parsing allows unquoted keys, string and enum values, and quoted integers + val data = format.decodeFromString(""" + { + name : kotlinx.serialization, + status : SUPPORTED, + votes : "9000" + } + """) + println(data) + // Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000) +} diff --git a/docs/guide/example/example-json-03.kt b/docs/guide/example/example-json-03.kt new file mode 100644 index 0000000000..920fe9140d --- /dev/null +++ b/docs/guide/example/example-json-03.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson03 + +val format = Json { ignoreUnknownKeys = true } + +@Serializable +data class Project(val name: String) + +fun main() { + val test = "testing" + val data = format.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) + println(test) +} diff --git a/docs/guide/example/example-json-04.kt b/docs/guide/example/example-json-04.kt new file mode 100644 index 0000000000..c16b41fa05 --- /dev/null +++ b/docs/guide/example/example-json-04.kt @@ -0,0 +1,12 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson04 + +@Serializable +data class Project(@JsonNames("title") val name: String) + +fun main() { + val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") + println(project) + val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") + println(oldProject) +} diff --git a/docs/guide/example/example-json-05.kt b/docs/guide/example/example-json-05.kt new file mode 100644 index 0000000000..ef61c8d4cc --- /dev/null +++ b/docs/guide/example/example-json-05.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson05 + +val format = Json { encodeDefaults = true } + +@Serializable +class Project( + val name: String, + val language: String = "Kotlin", + val website: String? = null +) + +fun main() { + val data = Project("kotlinx.serialization") + println(format.encodeToString(data)) +} diff --git a/docs/guide/example/example-json-06.kt b/docs/guide/example/example-json-06.kt new file mode 100644 index 0000000000..bbdbe469a2 --- /dev/null +++ b/docs/guide/example/example-json-06.kt @@ -0,0 +1,20 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson06 + +val format = Json { explicitNulls = false } + +@Serializable +data class Project( + val name: String, + val language: String, + val version: String? = "1.2.2", + val website: String?, + val description: String? = null +) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin", null, null, null) + val json = format.encodeToString(data) + println(json) + println(format.decodeFromString(json)) +} diff --git a/docs/guide/example/example-json-07.kt b/docs/guide/example/example-json-07.kt new file mode 100644 index 0000000000..6a5324dc2d --- /dev/null +++ b/docs/guide/example/example-json-07.kt @@ -0,0 +1,14 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson07 + +val format = Json { coerceInputValues = true } + +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = format.decodeFromString(""" + {"name":"kotlinx.serialization","language":null} + """) + println(data) +} diff --git a/docs/guide/example/example-json-08.kt b/docs/guide/example/example-json-08.kt new file mode 100644 index 0000000000..f98c27b47d --- /dev/null +++ b/docs/guide/example/example-json-08.kt @@ -0,0 +1,17 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson08 + +enum class Color { BLACK, WHITE } + +@Serializable +data class Brush(val foreground: Color = Color.BLACK, val background: Color?) + +val json = Json { + coerceInputValues = true + explicitNulls = false +} + +fun main() { + val brush = json.decodeFromString("""{"foreground":"pink", "background":"purple"}""") + println(brush) +} diff --git a/docs/guide/example/example-json-09.kt b/docs/guide/example/example-json-09.kt new file mode 100644 index 0000000000..e34a8767bc --- /dev/null +++ b/docs/guide/example/example-json-09.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson09 + +val format = Json { allowStructuredMapKeys = true } + +@Serializable +data class Project(val name: String) + +fun main() { + val map = mapOf( + Project("kotlinx.serialization") to "Serialization", + Project("kotlinx.coroutines") to "Coroutines" + ) + println(format.encodeToString(map)) +} diff --git a/docs/guide/example/example-json-10.kt b/docs/guide/example/example-json-10.kt new file mode 100644 index 0000000000..8f57e4b160 --- /dev/null +++ b/docs/guide/example/example-json-10.kt @@ -0,0 +1,14 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson10 + +val format = Json { allowSpecialFloatingPointValues = true } + +@Serializable +class Data( + val value: Double +) + +fun main() { + val data = Data(Double.NaN) + println(format.encodeToString(data)) +} diff --git a/docs/guide/example/example-json-11.kt b/docs/guide/example/example-json-11.kt new file mode 100644 index 0000000000..4ff16d571b --- /dev/null +++ b/docs/guide/example/example-json-11.kt @@ -0,0 +1,18 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson11 + +val format = Json { classDiscriminator = "#class" } + +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + println(format.encodeToString(data)) +} diff --git a/docs/guide/example/example-json-12.kt b/docs/guide/example/example-json-12.kt new file mode 100644 index 0000000000..15921501a2 --- /dev/null +++ b/docs/guide/example/example-json-12.kt @@ -0,0 +1,28 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson12 + +@Serializable +@JsonClassDiscriminator("message_type") +sealed class Base + +@Serializable // Class discriminator is inherited from Base +sealed class ErrorClass: Base() + +@Serializable +data class Message(val message: Base, val error: ErrorClass?) + +@Serializable +@SerialName("my.app.BaseMessage") +data class BaseMessage(val message: String) : Base() + +@Serializable +@SerialName("my.app.GenericError") +data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() + + +val format = Json { classDiscriminator = "#class" } + +fun main() { + val data = Message(BaseMessage("not found"), GenericError(404)) + println(format.encodeToString(data)) +} diff --git a/docs/guide/example/example-json-13.kt b/docs/guide/example/example-json-13.kt new file mode 100644 index 0000000000..6b232b14df --- /dev/null +++ b/docs/guide/example/example-json-13.kt @@ -0,0 +1,17 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson13 + +val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } + +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + println(format.encodeToString(data)) +} diff --git a/docs/guide/example/example-json-14.kt b/docs/guide/example/example-json-14.kt new file mode 100644 index 0000000000..284a726889 --- /dev/null +++ b/docs/guide/example/example-json-14.kt @@ -0,0 +1,13 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson14 + +val format = Json { decodeEnumsCaseInsensitive = true } + +enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } + +@Serializable +data class CasesList(val cases: List) + +fun main() { + println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) +} diff --git a/docs/guide/example/example-json-15.kt b/docs/guide/example/example-json-15.kt new file mode 100644 index 0000000000..c53713f136 --- /dev/null +++ b/docs/guide/example/example-json-15.kt @@ -0,0 +1,12 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson15 + +@Serializable +data class Project(val projectName: String, val projectOwner: String) + +val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase } + +fun main() { + val project = format.decodeFromString("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""") + println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) +} diff --git a/docs/guide/example/example-json-16.kt b/docs/guide/example/example-json-16.kt new file mode 100644 index 0000000000..aedbe85b2f --- /dev/null +++ b/docs/guide/example/example-json-16.kt @@ -0,0 +1,54 @@ +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. +package example.exampleJson16 + +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.descriptors.* +import kotlin.io.encoding.* + +@OptIn(ExperimentalEncodingApi::class) +object ByteArrayAsBase64Serializer : KSerializer { + private val base64 = Base64.Default + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor( + "ByteArrayAsBase64Serializer", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: ByteArray) { + val base64Encoded = base64.encode(value) + encoder.encodeString(base64Encoded) + } + + override fun deserialize(decoder: Decoder): ByteArray { + val base64Decoded = decoder.decodeString() + return base64.decode(base64Decoded) + } +} + +@Serializable +data class Value( + @Serializable(with = ByteArrayAsBase64Serializer::class) + val base64Input: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as Value + return base64Input.contentEquals(other.base64Input) + } + + override fun hashCode(): Int { + return base64Input.contentHashCode() + } +} + +fun main() { + val string = "foo string" + val value = Value(string.toByteArray()) + val encoded = Json.encodeToString(value) + println(encoded) + val decoded = Json.decodeFromString(encoded) + println(decoded.base64Input.decodeToString()) +} diff --git a/docs/guide/example/example-poly-01.kt b/docs/guide/example/example-poly-01.kt new file mode 100644 index 0000000000..80dbab158d --- /dev/null +++ b/docs/guide/example/example-poly-01.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +package example.examplePoly01 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +open class Project(val name: String) + +class OwnedProject(name: String, val owner: String) : Project(name) + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + println(Json.encodeToString(data)) +} diff --git a/docs/guide/example/example-serializer-01.kt b/docs/guide/example/example-serializer-01.kt new file mode 100644 index 0000000000..13aaa6a60b --- /dev/null +++ b/docs/guide/example/example-serializer-01.kt @@ -0,0 +1,13 @@ +// This file was automatically generated from serializers.md by Knit tool. Do not edit. +package example.exampleSerializer01 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +class Color(val rgb: Int) + +fun main() { + val green = Color(0x00ff00) + println(Json.encodeToString(green)) +} diff --git a/docs/guide/test/BasicSerializationTest.kt b/docs/guide/test/BasicSerializationTest.kt new file mode 100644 index 0000000000..11f9e9f28f --- /dev/null +++ b/docs/guide/test/BasicSerializationTest.kt @@ -0,0 +1,144 @@ +// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class BasicSerializationTest { + @Test + fun testExampleBasic01() { + captureOutput("ExampleBasic01") { example.exampleBasic01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.", + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + ) + } + + @Test + fun testExampleBasic02() { + captureOutput("ExampleBasic02") { example.exampleBasic02.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + ) + } + + @Test + fun testExampleBasic03() { + captureOutput("ExampleBasic03") { example.exampleBasic03.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleClasses01() { + captureOutput("ExampleClasses01") { example.exampleClasses01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"stars\":9000}" + ) + } + + @Test + fun testExampleClasses02() { + captureOutput("ExampleClasses02") { example.exampleClasses02.main() }.verifyOutputLines( + "{\"owner\":\"kotlin\",\"name\":\"kotlinx.serialization\"}" + ) + } + + @Test + fun testExampleClasses03() { + captureOutput("ExampleClasses03") { example.exampleClasses03.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" java.lang.IllegalArgumentException: name cannot be empty" + ) + } + + @Test + fun testExampleClasses04() { + captureOutput("ExampleClasses04") { example.exampleClasses04.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $" + ) + } + + @Test + fun testExampleClasses05() { + captureOutput("ExampleClasses05") { example.exampleClasses05.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleClasses06() { + captureOutput("ExampleClasses06") { example.exampleClasses06.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleClasses07() { + captureOutput("ExampleClasses07") { example.exampleClasses07.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $" + ) + } + + @Test + fun testExampleClasses08() { + captureOutput("ExampleClasses08") { example.exampleClasses08.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name", + "Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys." + ) + } + + @Test + fun testExampleClasses09() { + captureOutput("ExampleClasses09") { example.exampleClasses09.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\"}" + ) + } + + @Test + fun testExampleClasses10() { + captureOutput("ExampleClasses10") { example.exampleClasses10.main() }.verifyOutputLines( + "{\"name\":\"Alice\",\"projects\":[{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}]}", + "{\"name\":\"Bob\"}" + ) + } + + @Test + fun testExampleClasses11() { + captureOutput("ExampleClasses11") { example.exampleClasses11.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\"}" + ) + } + + @Test + fun testExampleClasses12() { + captureOutput("ExampleClasses12") { example.exampleClasses12.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language", + "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value." + ) + } + + @Test + fun testExampleClasses13() { + captureOutput("ExampleClasses13") { example.exampleClasses13.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"}}" + ) + } + + @Test + fun testExampleClasses14() { + captureOutput("ExampleClasses14") { example.exampleClasses14.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"maintainer\":{\"name\":\"kotlin\"}}" + ) + } + + @Test + fun testExampleClasses15() { + captureOutput("ExampleClasses15") { example.exampleClasses15.main() }.verifyOutputLines( + "{\"a\":{\"contents\":42},\"b\":{\"contents\":{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}}}" + ) + } + + @Test + fun testExampleClasses16() { + captureOutput("ExampleClasses16") { example.exampleClasses16.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"lang\":\"Kotlin\"}" + ) + } +} diff --git a/docs/guide/test/BuiltinClassesTest.kt b/docs/guide/test/BuiltinClassesTest.kt new file mode 100644 index 0000000000..1d3dcb40c9 --- /dev/null +++ b/docs/guide/test/BuiltinClassesTest.kt @@ -0,0 +1,99 @@ +// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class BuiltinClassesTest { + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"answer\":42,\"pi\":3.141592653589793}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"signature\":2067120338512882656}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"signature\":\"2067120338512882656\"}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"status\":\"SUPPORTED\"}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"status\":\"maintained\"}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"first\":1,\"second\":{\"name\":\"kotlinx.serialization\"}}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "[{\"name\":\"kotlinx.serialization\"},{\"name\":\"kotlinx.coroutines\"}]" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "[{\"name\":\"kotlinx.serialization\"},{\"name\":\"kotlinx.coroutines\"}]" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "Data(a=[42, 42], b=[42])" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"1\":{\"name\":\"kotlinx.serialization\"},\"2\":{\"name\":\"kotlinx.coroutines\"}}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{}", + "{}" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "\"PT16M40S\"" + ) + } + + @Test + fun testExampleBuiltin01() { + captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( + "{\"value\":42}" + ) + } +} diff --git a/docs/guide/test/FormatsTest.kt b/docs/guide/test/FormatsTest.kt new file mode 100644 index 0000000000..932f818a3b --- /dev/null +++ b/docs/guide/test/FormatsTest.kt @@ -0,0 +1,151 @@ +// This file was automatically generated from formats.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class FormatsTest { + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{BF}dnameukotlinx.serializationhlanguagefKotlin{FF}", + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{BF}etype2D{01}{02}{03}{04}etype4{9F}{05}{06}{07}{08}{FF}{FF}", + "Data(type2=[1, 2, 3, 4], type4=[5, 6, 7, 8])" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{0A}{15}kotlinx.serialization{12}{06}Kotlin", + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{0A}{15}kotlinx.serialization{1A}{06}Kotlin", + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{08}{01}{10}{03}{1D}{03}{00}{00}{00}" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{08}{01}{08}{02}{08}{03}", + "Data(a=[1, 2, 3], b=[])" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "0a03546f6d1203313233", + "0a054a657272791a03373839", + "Data(name=Tom, phone=HomePhone(number=123))", + "Data(name=Jerry, phone=WorkPhone(number=789))" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "syntax = \"proto2\";", + "", + "", + "// serial name 'example.exampleFormats09.SampleData'", + "message SampleData {", + " required int64 amount = 1;", + " optional string description = 2;", + " // WARNING: a default value decoded when value is missing", + " optional string department = 3;", + "}", + "" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "name = kotlinx.serialization", + "owner.name = kotlin" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "[kotlinx.serialization, kotlin, 9000]" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "[kotlinx.serialization, kotlin, 9000]", + "Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "[kotlinx.serialization, kotlin, 9000]", + "Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "[kotlinx.serialization, 2, kotlin, jetbrains, 9000]", + "Project(name=kotlinx.serialization, owners=[User(name=kotlin), User(name=jetbrains)], votes=9000)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "[kotlinx.serialization, !!, kotlin, NULL]", + "Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=null)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{00}{15}kotlinx.serialization{00}{06}Kotlin", + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleFormats01() { + captureOutput("ExampleFormats01") { example.exampleFormats01.main() }.verifyOutputLines( + "{00}{15}kotlinx.serialization{04}{0A}{0B}{0C}{0D}", + "Project(name=kotlinx.serialization, attachment=[10, 11, 12, 13])" + ) + } +} diff --git a/docs/guide/test/JsonTest.kt b/docs/guide/test/JsonTest.kt new file mode 100644 index 0000000000..0f8c36e81f --- /dev/null +++ b/docs/guide/test/JsonTest.kt @@ -0,0 +1,70 @@ +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class JsonTest { + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "9042" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "{", + " \"pi_double\": 3.141592653589793,", + " \"pi_string\": \"3.141592653589793238462643383279\"", + "}" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "{", + " \"pi_literal\": 3.141592653589793238462643383279,", + " \"pi_double\": 3.141592653589793,", + " \"pi_string\": \"3.141592653589793238462643383279\"", + "}" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLines( + "3.141592653589793238462643383279" + ) + } + + @Test + fun testExampleJson01() { + captureOutput("ExampleJson01") { example.exampleJson01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive" + ) + } +} diff --git a/docs/guide/test/PolymorphismTest.kt b/docs/guide/test/PolymorphismTest.kt new file mode 100644 index 0000000000..4f67914e7a --- /dev/null +++ b/docs/guide/test/PolymorphismTest.kt @@ -0,0 +1,154 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class PolymorphismTest { + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.coroutines\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found.", + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.", + "Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.", + "To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'." + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"example.examplePoly04.OwnedProject\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"owned\",\"status\":\"open\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "[{\"type\":\"example.examplePoly08.EmptyResponse\"},{\"type\":\"example.examplePoly08.TextResponse\",\"text\":\"OK\"}]" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLinesStart( + "{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.", + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.", + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"},\"any\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"OkResponse\",\"data\":{\"type\":\"OwnedProject\",\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"}}", + "OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin))" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $", + "Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule." + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)]" + ) + } + + @Test + fun testExamplePoly01() { + captureOutput("ExamplePoly01") { example.examplePoly01.main() }.verifyOutputLines( + "{\"type\":\"Cat\",\"catType\":\"Tabby\"}" + ) + } +} diff --git a/docs/guide/test/SerializersTest.kt b/docs/guide/test/SerializersTest.kt new file mode 100644 index 0000000000..171b1b95c4 --- /dev/null +++ b/docs/guide/test/SerializersTest.kt @@ -0,0 +1,170 @@ +// This file was automatically generated from serializers.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class SerializersTest { + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"rgb\":65280}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "Color(rgb: kotlin.Int)" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "Box(contents: Color)" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "PrimitiveDescriptor(kotlin.Int)" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "kotlin.collections.LinkedHashMap(PrimitiveDescriptor(kotlin.String), Color(rgb: kotlin.Int))" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "\"00ff00\"" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "65280" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"background\":\"ffffff\",\"foreground\":\"000000\"}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "[0,255,0]" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"r\":0,\"g\":255,\"b\":0}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"r\":0,\"g\":255,\"b\":0}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"r\":0,\"g\":255,\"b\":0}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "1455494400000" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"releaseDates\":[1688601600000,1682380800000,1672185600000]}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"stableReleaseDate\":\"2016-02-15\",\"lastReleaseTimestamp\":1657152000000}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\"}", + "Box(contents=Project(name=kotlinx.serialization))" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.", + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + ) + } + + @Test + fun testExampleSerializer01() { + captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"stars\":9000}" + ) + } +} diff --git a/docs/serialization.tree b/docs/serialization.tree index 4ccac354fb..bb86ff8cf9 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -13,8 +13,8 @@ - - + + diff --git a/docs/topics/basic-serialization.md b/docs/topics/basic-serialization.md index 935ffe987b..c6931c607c 100644 --- a/docs/topics/basic-serialization.md +++ b/docs/topics/basic-serialization.md @@ -98,7 +98,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-basic-02.kt). +> You can get the full code [here](../guide/example/example-basic-02.kt). The `@Serializable` annotation instructs the Kotlin Serialization plugin to automatically generate and hook up a _serializer_ for this class. Now the output of the example is the corresponding JSON. @@ -134,7 +134,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-basic-03.kt). +> You can get the full code [here](../guide/example/example-basic-03.kt). Running this code we get back the object. @@ -178,7 +178,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-01.kt). +> You can get the full code [here](../guide/example/example-classes-01.kt). We can clearly see that only the `name` and `stars` properties are present in the JSON output. @@ -228,7 +228,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-02.kt). +> You can get the full code [here](../guide/example/example-classes-02.kt). This example produces the expected output. @@ -265,7 +265,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-03.kt). +> You can get the full code [here](../guide/example/example-classes-03.kt). Running this code produces the exception: @@ -292,7 +292,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-04.kt). +> You can get the full code [here](../guide/example/example-classes-04.kt). It produces the exception: @@ -317,7 +317,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-05.kt). +> You can get the full code [here](../guide/example/example-classes-05.kt). It produces the following output with the default value for the `language` property. @@ -350,7 +350,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-06.kt). +> You can get the full code [here](../guide/example/example-classes-06.kt). Since the `language` property was specified in the input, we don't see the "Computing" string printed in the output. @@ -378,7 +378,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-07.kt). +> You can get the full code [here](../guide/example/example-classes-07.kt). We get the following exception. @@ -405,7 +405,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-08.kt). +> You can get the full code [here](../guide/example/example-classes-08.kt). Attempts to explicitly specify its value in the serial format, even if the specified value is equal to the default one, produces the following exception. @@ -434,7 +434,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-09.kt). +> You can get the full code [here](../guide/example/example-classes-09.kt). It produces the following output, which does not have the `language` property because its value is equal to the default one. @@ -475,7 +475,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-10.kt). +> You can get the full code [here](../guide/example/example-classes-10.kt). As you can see, `language` property is preserved and `projects` is omitted: @@ -500,7 +500,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-11.kt). +> You can get the full code [here](../guide/example/example-classes-11.kt). This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded). @@ -527,7 +527,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-12.kt). +> You can get the full code [here](../guide/example/example-classes-12.kt). Even though the `language` property has a default value, it is still an error to attempt to assign the `null` value to it. @@ -561,7 +561,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-13.kt). +> You can get the full code [here](../guide/example/example-classes-13.kt). When encoded to JSON it results in a nested JSON object. @@ -594,7 +594,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-14.kt). +> You can get the full code [here](../guide/example/example-classes-14.kt). We simply get the `owner` value encoded twice. @@ -638,7 +638,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-15.kt). +> You can get the full code [here](../guide/example/example-classes-15.kt). The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`. @@ -667,7 +667,7 @@ fun main() { } ``` -> You can get the full code [here](../../guide/example/example-classes-16.kt). +> You can get the full code [here](../guide/example/example-classes-16.kt). Now we see that an abbreviated name `lang` is used in the JSON output. diff --git a/docs/topics/configure-json-serialization.md b/docs/topics/configure-json-serialization.md index a29d051440..c128c54d85 100644 --- a/docs/topics/configure-json-serialization.md +++ b/docs/topics/configure-json-serialization.md @@ -1,3 +1,62 @@ -[//]: # (title: Customizing and tranforming JSON serialization) +[//]: # (title: JSON serialization overview) -INTRO - TBD \ No newline at end of file +JSON serialization in Kotlin allows you to easily convert Kotlin objects to JSON and back. +The [`Json`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/) class is the primary tool for this, offering flexibility in how JSON is generated and parsed. +You can configure `Json` instances to handle specific JSON behaviors or use it as is for basic tasks. + +The key features include: + +* Serialization of Kotlin objects to JSON strings using `Json.encodeToString`. +* Deserialization of JSON strings back into Kotlin objects with `Json.decodeFromString`. +* Working directly with `JsonElement` for more complex JSON structures using `Json.encodeToJsonElement` and `Json.decodeFromJsonElement`. + +Import the necessary libraries to use the `Json` class for JSON serialization and deserialization: + +```kotlin +import kotlinx.serialization.* +import kotlinx.serialization.json.* +``` + +Let's look at a simple example: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.Json + +@Serializable +data class User(val name: String, val age: Int) + +fun main() { + // Creates a Json instance with default settings + val json = Json {} + + // Creates a User object + val user = User("Alice", 30) + + // Converts the User object to a JSON string + val jsonString = json.encodeToString(user) + println(jsonString) + // {"name":"Alice","age":30} + + // Converts the JSON string back to a User object + val deserializedUser = json.decodeFromString(jsonString) + println(deserializedUser) + // User(name=Alice, age=30) +} +``` + +Additionally, you can [customize the `Json` instance](serialization-json-configuration.md) to handle specific needs, such as ignoring unknown keys: + +```kotlin +// Configures a Json instance to ignore unknown keys +val customJson = Json { + ignoreUnknownKeys = true +} +``` + +## What's next? + +* Learn how to [customize JSON serialization settings](serialization-json-configuration.md) to fit your specific needs. +* Explore [advanced JSON element handling](serialization-json-elements.md) to manipulate and work with JSON data before it is parsed or serialized. +* Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. \ No newline at end of file diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index 6cec50b251..c4ce52d68e 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -22,4 +22,92 @@ object ColorAsStringSerializer : KSerializer { return Color(string.toInt(16)) } } -``` \ No newline at end of file +``` + +### Base64 + +To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default +implementation of Kotlin's Base64 encoder. Note that some serializers use different RFCs for Base64 encoding by default. +For example, Jackson uses a variant of [Base64 Mime](https://datatracker.ietf.org/doc/html/rfc2045). The same result in +kotlinx.serialization can be achieved with Base64.Mime encoder. +[Kotlin's documentation for Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) lists +other available encoders. + +```kotlin +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.descriptors.* +import kotlin.io.encoding.* + +@OptIn(ExperimentalEncodingApi::class) +object ByteArrayAsBase64Serializer : KSerializer { + private val base64 = Base64.Default + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor( + "ByteArrayAsBase64Serializer", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: ByteArray) { + val base64Encoded = base64.encode(value) + encoder.encodeString(base64Encoded) + } + + override fun deserialize(decoder: Decoder): ByteArray { + val base64Decoded = decoder.decodeString() + return base64.decode(base64Decoded) + } +} +``` + +For more details on how to create your own custom serializer, you can +see [custom serializers](serializers.md#custom-serializers). + +Then we can use it like this: + +```kotlin +@Serializable +data class Value( + @Serializable(with = ByteArrayAsBase64Serializer::class) + val base64Input: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as Value + return base64Input.contentEquals(other.base64Input) + } + + override fun hashCode(): Int { + return base64Input.contentHashCode() + } +} + +fun main() { + val string = "foo string" + val value = Value(string.toByteArray()) + val encoded = Json.encodeToString(value) + println(encoded) + val decoded = Json.decodeFromString(encoded) + println(decoded.base64Input.decodeToString()) +} +``` + +> You can get the full code [here](../../guide/example/example-json-16.kt) + +```text +{"base64Input":"Zm9vIHN0cmluZw=="} +foo string +``` + +Notice the serializer we wrote is not dependent on `Json` format, therefore, it can be used in any format. + +For projects that use this serializer in many places, to avoid specifying the serializer every time, it is possible +to [specify a serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias). +For example: +````kotlin +typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray +```` + + \ No newline at end of file diff --git a/docs/topics/json.md b/docs/topics/json.md index 606054051d..321c61cfe7 100644 --- a/docs/topics/json.md +++ b/docs/topics/json.md @@ -1,4 +1,3 @@ - # JSON features @@ -8,1416 +7,8 @@ In this chapter, we'll walk through features of [JSON](https://www.json.org/json **Table of contents** - -* [Json configuration](#json-configuration) - * [Pretty printing](#pretty-printing) - * [Lenient parsing](#lenient-parsing) - * [Ignoring unknown keys](#ignoring-unknown-keys) - * [Alternative Json names](#alternative-json-names) - * [Encoding defaults](#encoding-defaults) - * [Explicit nulls](#explicit-nulls) - * [Coercing input values](#coercing-input-values) - * [Allowing structured map keys](#allowing-structured-map-keys) - * [Allowing special floating-point values](#allowing-special-floating-point-values) - * [Class discriminator for polymorphism](#class-discriminator-for-polymorphism) - * [Class discriminator output mode](#class-discriminator-output-mode) - * [Decoding enums in a case-insensitive manner](#decoding-enums-in-a-case-insensitive-manner) - * [Global naming strategy](#global-naming-strategy) - * [Base64](#base64) -* [Json elements](#json-elements) - * [Parsing to Json element](#parsing-to-json-element) - * [Types of Json elements](#types-of-json-elements) - * [Json element builders](#json-element-builders) - * [Decoding Json elements](#decoding-json-elements) - * [Encoding literal Json content (experimental)](#encoding-literal-json-content-experimental) - * [Serializing large decimal numbers](#serializing-large-decimal-numbers) - * [Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden](#using-jsonunquotedliteral-to-create-a-literal-unquoted-value-of-null-is-forbidden) -* [Json transformations](#json-transformations) - * [Array wrapping](#array-wrapping) - * [Array unwrapping](#array-unwrapping) - * [Manipulating default values](#manipulating-default-values) - * [Content-based polymorphic deserialization](#content-based-polymorphic-deserialization) - * [Under the hood (experimental)](#under-the-hood-experimental) - * [Maintaining custom JSON attributes](#maintaining-custom-json-attributes) - -## Json configuration - -The default [Json] implementation is quite strict with respect to invalid inputs. It enforces Kotlin type safety and -restricts Kotlin values that can be serialized so that the resulting JSON representations are standard. -Many non-standard JSON features are supported by creating a custom instance of a JSON _format_. - -To use a custom JSON format configuration, create your own [Json] class instance from an existing -instance, such as a default `Json` object, using the [Json()] builder function. Specify parameter values -in the parentheses via the [JsonBuilder] DSL. The resulting `Json` format instance is immutable and thread-safe; -it can be simply stored in a top-level property. - -> We recommend that you store and reuse custom instances of formats for performance reasons because format implementations -> may cache format-specific additional information about the classes they serialize. - -This chapter shows configuration features that [Json] supports. - - - -### Pretty printing - -By default, the [Json] output is a single line. You can configure it to pretty print the output (that is, add indentations -and line breaks for better readability) by setting the [prettyPrint][JsonBuilder.prettyPrint] property to `true`: - -```kotlin -val format = Json { prettyPrint = true } - -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-01.kt). - -It gives the following nice result: - -```text -{ - "name": "kotlinx.serialization", - "language": "Kotlin" -} -``` - - - -### Lenient parsing - -By default, [Json] parser enforces various JSON restrictions to be as specification-compliant as possible -(see [RFC-4627]). Particularly, keys and string literals must be quoted. Those restrictions can be relaxed with -the [isLenient][JsonBuilder.isLenient] property. With `isLenient = true`, you can parse quite freely-formatted data: - -```kotlin -val format = Json { isLenient = true } - -enum class Status { SUPPORTED } - -@Serializable -data class Project(val name: String, val status: Status, val votes: Int) - -fun main() { - val data = format.decodeFromString(""" - { - name : kotlinx.serialization, - status : SUPPORTED, - votes : "9000" - } - """) - println(data) -} -``` - -> You can get the full code [here](../../guide/example/example-json-02.kt). - -You get the object, even though all keys of the source JSON, string, and enum values are unquoted, while an -integer is quoted: - -```text -Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000) -``` - - - -### Ignoring unknown keys - -JSON format is often used to read the output of third-party services or in other dynamic environments where -new properties can be added during the API evolution. By default, unknown keys encountered during deserialization produce an error. -You can avoid this and just ignore such keys by setting the [ignoreUnknownKeys][JsonBuilder.ignoreUnknownKeys] property -to `true`: - -```kotlin -val format = Json { ignoreUnknownKeys = true } - -@Serializable -data class Project(val name: String) - -fun main() { - val data = format.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) -} -``` - -> You can get the full code [here](../../guide/example/example-json-03.kt). - -It decodes the object despite the fact that the `Project` class doesn't have the `language` property: - -```text -Project(name=kotlinx.serialization) -``` - - - -### Alternative Json names - -It's not a rare case when JSON fields are renamed due to a schema version change. -You can use the [`@SerialName` annotation](basic-serialization.md#serial-field-names) to change the name of a JSON field, -but such renaming blocks the ability to decode data with the old name. -To support multiple JSON names for the one Kotlin property, there is the [JsonNames] annotation: - -```kotlin -@Serializable -data class Project(@JsonNames("title") val name: String) - -fun main() { - val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") - println(project) - val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") - println(oldProject) -} -``` - -> You can get the full code [here](../../guide/example/example-json-04.kt). - -As you can see, both `name` and `title` Json fields correspond to `name` property: - -```text -Project(name=kotlinx.serialization) -Project(name=kotlinx.coroutines) -``` - -Support for [JsonNames] annotation is controlled by the [JsonBuilder.useAlternativeNames] flag. -Unlike most of the configuration flags, this one is enabled by default and does not need attention -unless you want to do some fine-tuning. - - - -### Encoding defaults - -Default values of properties are not encoded by default because they will be assigned to missing fields during decoding anyway. -See the [Defaults are not encoded](basic-serialization.md#defaults-are-not-encoded-by-default) section for details and an example. -This is especially useful for nullable properties with null defaults and avoids writing the corresponding null values. -The default behavior can be changed by setting the [encodeDefaults][JsonBuilder.encodeDefaults] property to `true`: - -```kotlin -val format = Json { encodeDefaults = true } - -@Serializable -class Project( - val name: String, - val language: String = "Kotlin", - val website: String? = null -) - -fun main() { - val data = Project("kotlinx.serialization") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-05.kt). - -It produces the following output which encodes all the property values including the default ones: - -```text -{"name":"kotlinx.serialization","language":"Kotlin","website":null} -``` - - - -### Explicit nulls - -By default, all `null` values are encoded into JSON strings, but in some cases you may want to omit them. -The encoding of `null` values can be controlled with the [explicitNulls][JsonBuilder.explicitNulls] property. - -If you set property to `false`, fields with `null` values are not encoded into JSON even if the property does not have a -default `null` value. When decoding such JSON, the absence of a property value is treated as `null` for nullable properties -without a default value. - -```kotlin -val format = Json { explicitNulls = false } - -@Serializable -data class Project( - val name: String, - val language: String, - val version: String? = "1.2.2", - val website: String?, - val description: String? = null -) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin", null, null, null) - val json = format.encodeToString(data) - println(json) - println(format.decodeFromString(json)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-06.kt). - -As you can see, `version`, `website` and `description` fields are not present in output JSON on the first line. -After decoding, the missing nullable property `website` without a default values has received a `null` value, -while nullable properties `version` and `description` are filled with their default values: - -```text -{"name":"kotlinx.serialization","language":"Kotlin"} -Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null) -``` - -> Pay attention to the fact that `version` was `null` before encoding and became `1.2.2` after decoding. -> Encoding/decoding of properties like this — nullable with a non-null default — becomes asymmetrical if `explicitNulls` is set to `false`. - -It is possible to make the decoder treat some invalid input data as a missing field to enhance the functionality of this flag. -See [coerceInputValues](#coercing-input-values) below for details. - -`explicitNulls` is `true` by default as it is the default behavior across different versions of the library. - - - -### Coercing input values - -JSON formats that from third parties can evolve, sometimes changing the field types. -This can lead to exceptions during decoding when the actual values do not match the expected values. -The default [Json] implementation is strict with respect to input types as was demonstrated in -the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section. You can relax this restriction -using the [coerceInputValues][JsonBuilder.coerceInputValues] property. - -This property only affects decoding. It treats a limited subset of invalid input values as if the -corresponding property was missing. -The current list of supported invalid values is: - -* `null` inputs for non-nullable types -* unknown values for enums - -If value is missing, it is replaced either with a default property value if it exists, -or with a `null` if [explicitNulls](#explicit-nulls) flag is set to `false` and a property is nullable (for enums). - -> This list may be expanded in the future, so that [Json] instance configured with this property becomes even more -> permissive to invalid value in the input, replacing them with defaults or nulls. - -See the example from the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section: - -```kotlin -val format = Json { coerceInputValues = true } - -@Serializable -data class Project(val name: String, val language: String = "Kotlin") - -fun main() { - val data = format.decodeFromString(""" - {"name":"kotlinx.serialization","language":null} - """) - println(data) -} -``` - -> You can get the full code [here](../../guide/example/example-json-07.kt). - -The invalid `null` value for the `language` property was coerced into the default value: - -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -Example of using this flag together with [explicitNulls](#explicit-nulls) to coerce invalid enum values: - -```kotlin -enum class Color { BLACK, WHITE } - -@Serializable -data class Brush(val foreground: Color = Color.BLACK, val background: Color?) - -val json = Json { - coerceInputValues = true - explicitNulls = false -} - -fun main() { - val brush = json.decodeFromString("""{"foreground":"pink", "background":"purple"}""") - println(brush) -} -``` - -> You can get the full code [here](../../guide/example/example-json-08.kt). - -Despite that we do not have `Color.pink` and `Color.purple` colors, `decodeFromString` function returns successfully: - -```text -Brush(foreground=BLACK, background=null) -``` - -`foreground` property received its default value, and `background` property received `null` because of `explicitNulls = false` setting. - - - -### Allowing structured map keys - -JSON format does not natively support the concept of a map with structured keys. Keys in JSON objects -are strings and can be used to represent only primitives or enums by default. -You can enable non-standard support for structured keys with -the [allowStructuredMapKeys][JsonBuilder.allowStructuredMapKeys] property. - -This is how you can serialize a map with keys of a user-defined class: - -```kotlin -val format = Json { allowStructuredMapKeys = true } - -@Serializable -data class Project(val name: String) - -fun main() { - val map = mapOf( - Project("kotlinx.serialization") to "Serialization", - Project("kotlinx.coroutines") to "Coroutines" - ) - println(format.encodeToString(map)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-09.kt). - -The map with structured keys gets represented as JSON array with the following items: `[key1, value1, key2, value2,...]`. - -```text -[{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"] -``` - - - -### Allowing special floating-point values - -By default, special floating-point values like [Double.NaN] and infinities are not supported in JSON because -the JSON specification prohibits it. -You can enable their encoding using the [allowSpecialFloatingPointValues][JsonBuilder.allowSpecialFloatingPointValues] -property: - -```kotlin -val format = Json { allowSpecialFloatingPointValues = true } - -@Serializable -class Data( - val value: Double -) - -fun main() { - val data = Data(Double.NaN) - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-10.kt). - -This example produces the following non-stardard JSON output, yet it is a widely used encoding for -special values in JVM world: - -```text -{"value":NaN} -``` - - - -### Class discriminator for polymorphism - -A key name that specifies a type when you have a polymorphic data can be specified -in the [classDiscriminator][JsonBuilder.classDiscriminator] property: - -```kotlin -val format = Json { classDiscriminator = "#class" } - -@Serializable -sealed class Project { - abstract val name: String -} - -@Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-11.kt). - -In combination with an explicitly specified [SerialName] of the class it provides full -control over the resulting JSON object: - -```text -{"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} -``` - - - -It is also possible to specify different class discriminators for different hierarchies. Instead of Json instance property, use [JsonClassDiscriminator] annotation directly on base serializable class: - -```kotlin -@Serializable -@JsonClassDiscriminator("message_type") -sealed class Base -``` - -This annotation is _inheritable_, so all subclasses of `Base` will have the same discriminator: - -```kotlin -@Serializable // Class discriminator is inherited from Base -sealed class ErrorClass: Base() -``` - -> To learn more about inheritable serial annotations, see documentation for [InheritableSerialInfo]. - -Note that it is not possible to explicitly specify different class discriminators in subclasses of `Base`. Only hierarchies with empty intersections can have different discriminators. - -Discriminator specified in the annotation has priority over discriminator in Json configuration: - - - -```kotlin - -val format = Json { classDiscriminator = "#class" } - -fun main() { - val data = Message(BaseMessage("not found"), GenericError(404)) - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-12.kt). - -As you can see, discriminator from the `Base` class is used: - -```text -{"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}} -``` - - - -### Class discriminator output mode - -Class discriminator provides information for serializing and deserializing [polymorphic class hierarchies](polymorphism.md#sealed-classes). -As shown above, it is only added for polymorphic classes by default. -In case you want to encode more or less information for various third party APIs about types in the output, it is possible to control -addition of the class discriminator with the [JsonBuilder.classDiscriminatorMode] property. - -For example, [ClassDiscriminatorMode.NONE] does not add class discriminator at all, in case the receiving party is not interested in Kotlin types: - -```kotlin -val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } - -@Serializable -sealed class Project { - abstract val name: String -} - -@Serializable -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-13.kt). - -Note that it would be impossible to deserialize this output back with kotlinx.serialization. - -```text -{"name":"kotlinx.coroutines","owner":"kotlin"} -``` - -Two other available values are [ClassDiscriminatorMode.POLYMORPHIC] (default behavior) and [ClassDiscriminatorMode.ALL_JSON_OBJECTS] (adds discriminator whenever possible). -Consult their documentation for details. - - - -### Decoding enums in a case-insensitive manner - -[Kotlin's naming policy recommends](https://kotlinlang.org/docs/coding-conventions.html#property-names) naming enum values -using either uppercase underscore-separated names or upper camel case names. -[Json] uses exact Kotlin enum values names for decoding by default. -However, sometimes third-party JSONs have such values named in lowercase or some mixed case. -In this case, it is possible to decode enum values in a case-insensitive manner using [JsonBuilder.decodeEnumsCaseInsensitive] property: - -```kotlin -val format = Json { decodeEnumsCaseInsensitive = true } - -enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } - -@Serializable -data class CasesList(val cases: List) - -fun main() { - println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) -} -``` - -> You can get the full code [here](../../guide/example/example-json-14.kt). - -It affects serial names as well as alternative names specified with [JsonNames] annotation, so both values are successfully decoded: - -```text -CasesList(cases=[VALUE_A, VALUE_B]) -``` - -This property does not affect encoding in any way. - - - -### Global naming strategy - -If properties' names in Json input are different from Kotlin ones, it is recommended to specify the name -for each property explicitly using [`@SerialName` annotation](basic-serialization.md#serial-field-names). -However, there are certain situations where transformation should be applied to every serial name — such as migration -from other frameworks or legacy codebase. For that cases, it is possible to specify a [namingStrategy][JsonBuilder.namingStrategy] -for a [Json] instance. `kotlinx.serialization` provides one strategy implementation out of the box, the [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html): - -```kotlin -@Serializable -data class Project(val projectName: String, val projectOwner: String) - -val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase } - -fun main() { - val project = format.decodeFromString("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""") - println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) -} -``` - -> You can get the full code [here](../../guide/example/example-json-15.kt). - -As you can see, both serialization and deserialization work as if all serial names are transformed from camel case to snake case: - -```text -{"project_name":"kotlinx.serialization","project_owner":"Kotlin"} -``` - -There are some caveats one should remember while dealing with a [JsonNamingStrategy]: - -* Due to the nature of the `kotlinx.serialization` framework, naming strategy transformation is applied to all properties regardless -of whether their serial name was taken from the property name or provided by [SerialName] annotation. -Effectively, it means one cannot avoid transformation by explicitly specifying the serial name. To be able to deserialize -non-transformed names, [JsonNames] annotation can be used instead. - -* Collision of the transformed name with any other (transformed) properties serial names or any alternative names -specified with [JsonNames] will lead to a deserialization exception. - -* Global naming strategies are very implicit: by looking only at the definition of the class, -it is impossible to determine which names it will have in the serialized form. -As a consequence, naming strategies are not friendly to actions like Find Usages/Rename in IDE, full-text search by grep, etc. -For them, the original name and the transformed are two different things; -changing one without the other may introduce bugs in many unexpected ways and lead to greater maintenance efforts for code with global naming strategies. - -Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application. - - - -### Base64 - -To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default -implementation of Kotlin's Base64 encoder. Note that some serializers use different RFCs for Base64 encoding by default. -For example, Jackson uses a variant of [Base64 Mime](https://datatracker.ietf.org/doc/html/rfc2045). The same result in -kotlinx.serialization can be achieved with Base64.Mime encoder. -[Kotlin's documentation for Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) lists -other available encoders. - -```kotlin -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.descriptors.* -import kotlin.io.encoding.* - -@OptIn(ExperimentalEncodingApi::class) -object ByteArrayAsBase64Serializer : KSerializer { - private val base64 = Base64.Default - - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor( - "ByteArrayAsBase64Serializer", - PrimitiveKind.STRING - ) - - override fun serialize(encoder: Encoder, value: ByteArray) { - val base64Encoded = base64.encode(value) - encoder.encodeString(base64Encoded) - } - - override fun deserialize(decoder: Decoder): ByteArray { - val base64Decoded = decoder.decodeString() - return base64.decode(base64Decoded) - } -} -``` - -For more details on how to create your own custom serializer, you can -see [custom serializers](serializers.md#custom-serializers). - -Then we can use it like this: - -```kotlin -@Serializable -data class Value( - @Serializable(with = ByteArrayAsBase64Serializer::class) - val base64Input: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - other as Value - return base64Input.contentEquals(other.base64Input) - } - - override fun hashCode(): Int { - return base64Input.contentHashCode() - } -} - -fun main() { - val string = "foo string" - val value = Value(string.toByteArray()) - val encoded = Json.encodeToString(value) - println(encoded) - val decoded = Json.decodeFromString(encoded) - println(decoded.base64Input.decodeToString()) -} -``` - -> You can get the full code [here](../../guide/example/example-json-16.kt) - -```text -{"base64Input":"Zm9vIHN0cmluZw=="} -foo string -``` - -Notice the serializer we wrote is not dependent on `Json` format, therefore, it can be used in any format. - -For projects that use this serializer in many places, to avoid specifying the serializer every time, it is possible -to [specify a serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias). -For example: -````kotlin -typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray -```` - - - -## Json elements - -Aside from direct conversions between strings and JSON objects, Kotlin serialization offers APIs that allow -other ways of working with JSON in the code. For example, you might need to tweak the data before it can parse -or otherwise work with such an unstructured data that it does not readily fit into the typesafe world of Kotlin -serialization. - -The main concept in this part of the library is [JsonElement]. Read on to learn what you can do with it. - -### Parsing to Json element - -A string can be _parsed_ into an instance of [JsonElement] with the [Json.parseToJsonElement] function. -It is called neither decoding nor deserialization because none of that happens in the process. -It just parses a JSON and forms an object representing it: - -```kotlin -fun main() { - val element = Json.parseToJsonElement(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(element) -} -``` - -> You can get the full code [here](../../guide/example/example-json-17.kt). - -A `JsonElement` prints itself as a valid JSON: - -```text -{"name":"kotlinx.serialization","language":"Kotlin"} -``` - - - -### Types of Json elements - -A [JsonElement] class has three direct subtypes, closely following JSON grammar: - -* [JsonPrimitive] represents primitive JSON elements, such as string, number, boolean, and null. - Each primitive has a simple string [content][JsonPrimitive.content]. There is also a - [JsonPrimitive()] constructor function overloaded to accept various primitive Kotlin types and - to convert them to `JsonPrimitive`. - -* [JsonArray] represents a JSON `[...]` array. It is a Kotlin [List] of `JsonElement` items. - -* [JsonObject] represents a JSON `{...}` object. It is a Kotlin [Map] from `String` keys to `JsonElement` values. - -The `JsonElement` class has extensions that cast it to its corresponding subtypes: -[jsonPrimitive][_jsonPrimitive], [jsonArray][_jsonArray], [jsonObject][_jsonObject]. The `JsonPrimitive` class, -in turn, provides converters to Kotlin primitive types: [int], [intOrNull], [long], [longOrNull], -and similar ones for other types. This is how you can use them for processing JSON whose structure you know: - -```kotlin -fun main() { - val element = Json.parseToJsonElement(""" - { - "name": "kotlinx.serialization", - "forks": [{"votes": 42}, {"votes": 9000}, {}] - } - """) - val sum = element - .jsonObject["forks"]!! - .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 } - println(sum) -} -``` - -> You can get the full code [here](../../guide/example/example-json-18.kt). - -The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`: - -```text -9042 -``` - - - -Note that the execution will fail if the structure of the data is otherwise different. - -### Json element builders - -You can construct instances of specific [JsonElement] subtypes using the respective builder functions -[buildJsonArray] and [buildJsonObject]. They provide a DSL to define the resulting JSON structure. It -is similar to Kotlin standard library collection builders, but with a JSON-specific convenience -of more type-specific overloads and inner builder functions. The following example shows -all the key features: - -```kotlin -fun main() { - val element = buildJsonObject { - put("name", "kotlinx.serialization") - putJsonObject("owner") { - put("name", "kotlin") - } - putJsonArray("forks") { - addJsonObject { - put("votes", 42) - } - addJsonObject { - put("votes", 9000) - } - } - } - println(element) -} -``` - -> You can get the full code [here](../../guide/example/example-json-19.kt). - -As a result, you get a proper JSON string: - -```text -{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} -``` - - - -### Decoding Json elements - -An instance of the [JsonElement] class can be decoded into a serializable object using -the [Json.decodeFromJsonElement] function: - -```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val element = buildJsonObject { - put("name", "kotlinx.serialization") - put("language", "Kotlin") - } - val data = Json.decodeFromJsonElement(element) - println(data) -} -``` - -> You can get the full code [here](../../guide/example/example-json-20.kt). - -The result is exactly what you would expect: - -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -### Encoding literal Json content (experimental) - -> This functionality is experimental and requires opting-in to [the experimental Kotlinx Serialization API](compatibility.md#experimental-api). - -In some cases it might be necessary to encode an arbitrary unquoted value. -This can be achieved with [JsonUnquotedLiteral]. - -#### Serializing large decimal numbers - -The JSON specification does not restrict the size or precision of numbers, however it is not possible to serialize -numbers of arbitrary size or precision using [JsonPrimitive()]. - -If [Double] is used, then the numbers are limited in precision, meaning that large numbers are truncated. -When using Kotlin/JVM [BigDecimal] can be used instead, but [JsonPrimitive()] will encode the value as a string, not a -number. - -```kotlin -import java.math.BigDecimal - -val format = Json { prettyPrint = true } - -fun main() { - val pi = BigDecimal("3.141592653589793238462643383279") - - val piJsonDouble = JsonPrimitive(pi.toDouble()) - val piJsonString = JsonPrimitive(pi.toString()) - - val piObject = buildJsonObject { - put("pi_double", piJsonDouble) - put("pi_string", piJsonString) - } - - println(format.encodeToString(piObject)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-21.kt). - -Even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. -The [Double] value is truncated to 15 decimal places, and the String is wrapped in quotes - which is not a JSON number. - -```text -{ - "pi_double": 3.141592653589793, - "pi_string": "3.141592653589793238462643383279" -} -``` - - - -To avoid precision loss, the string value of `pi` can be encoded using [JsonUnquotedLiteral]. - -```kotlin -import java.math.BigDecimal - -val format = Json { prettyPrint = true } - -fun main() { - val pi = BigDecimal("3.141592653589793238462643383279") - - // use JsonUnquotedLiteral to encode raw JSON content - val piJsonLiteral = JsonUnquotedLiteral(pi.toString()) - - val piJsonDouble = JsonPrimitive(pi.toDouble()) - val piJsonString = JsonPrimitive(pi.toString()) - - val piObject = buildJsonObject { - put("pi_literal", piJsonLiteral) - put("pi_double", piJsonDouble) - put("pi_string", piJsonString) - } - - println(format.encodeToString(piObject)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-22.kt). - -`pi_literal` now accurately matches the value defined. - -```text -{ - "pi_literal": 3.141592653589793238462643383279, - "pi_double": 3.141592653589793, - "pi_string": "3.141592653589793238462643383279" -} -``` - - - -To decode `pi` back to a [BigDecimal], the string content of the [JsonPrimitive] can be used. - -(This demonstration uses a [JsonPrimitive] for simplicity. For a more re-usable method of handling serialization, see -[Json Transformations](#json-transformations) below.) - - -```kotlin -import java.math.BigDecimal - -fun main() { - val piObjectJson = """ - { - "pi_literal": 3.141592653589793238462643383279 - } - """.trimIndent() - - val piObject: JsonObject = Json.decodeFromString(piObjectJson) - - val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content - - val pi = BigDecimal(piJsonLiteral) - - println(pi) -} -``` - -> You can get the full code [here](../../guide/example/example-json-23.kt). - -The exact value of `pi` is decoded, with all 30 decimal places of precision that were in the source JSON. - -```text -3.141592653589793238462643383279 -``` - - - -#### Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden - -To avoid creating an inconsistent state, encoding a String equal to `"null"` is forbidden. -Use [JsonNull] or [JsonPrimitive] instead. - -```kotlin -fun main() { - // caution: creating null with JsonUnquotedLiteral will cause an exception! - JsonUnquotedLiteral("null") -} -``` - -> You can get the full code [here](../../guide/example/example-json-24.kt). - -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive -``` - - - - -## Json transformations - -To affect the shape and contents of JSON output after serialization, or adapt input to deserialization, -it is possible to write a [custom serializer](serializers.md). However, it may be inconvenient to -carefully follow [Encoder] and [Decoder] calling conventions, especially for relatively small and easy tasks. -For that purpose, Kotlin serialization provides an API that can reduce the burden of implementing a custom -serializer to a problem of manipulating a Json elements tree. - -We recommend that you get familiar with the [Serializers](serializers.md) chapter: among other things, it -explains how custom serializers are bound to classes. - -Transformation capabilities are provided by the abstract [JsonTransformingSerializer] class which implements [KSerializer]. -Instead of direct interaction with `Encoder` or `Decoder`, this class asks you to supply transformations for JSON tree -represented by the [JsonElement] class using the`transformSerialize` and -`transformDeserialize` methods. Let's take a look at the examples. - -### Array wrapping - -The first example is an implementation of JSON array wrapping for lists. - -Consider a REST API that returns a JSON array of `User` objects, or a single object (not wrapped into an array) if there -is only one element in the result. - -In the data model, use the [`@Serializable`][Serializable] annotation to specify a custom serializer for a -`users: List` property. - - - -```kotlin -@Serializable -data class Project( - val name: String, - @Serializable(with = UserListSerializer::class) - val users: List -) - -@Serializable -data class User(val name: String) -``` - -Since this example covers only the deserialization case, you can implement `UserListSerializer` and override only the -`transformDeserialize` function. The `JsonTransformingSerializer` constructor takes an original serializer -as parameter (this approach is shown in the section [Constructing collection serializers](serializers.md#constructing-collection-serializers)): - -```kotlin -object UserListSerializer : JsonTransformingSerializer>(ListSerializer(User.serializer())) { - // If response is not an array, then it is a single object that should be wrapped into the array - override fun transformDeserialize(element: JsonElement): JsonElement = - if (element !is JsonArray) JsonArray(listOf(element)) else element -} -``` - -Now you can test the code with a JSON array or a single JSON object as inputs. - -```kotlin -fun main() { - println(Json.decodeFromString(""" - {"name":"kotlinx.serialization","users":{"name":"kotlin"}} - """)) - println(Json.decodeFromString(""" - {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]} - """)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-25.kt). - -The output shows that both cases are correctly deserialized into a Kotlin [List]. - -```text -Project(name=kotlinx.serialization, users=[User(name=kotlin)]) -Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) -``` - - - -### Array unwrapping - -You can also implement the `transformSerialize` function to unwrap a single-element list into a single JSON object -during serialization: - - - -```kotlin - override fun transformSerialize(element: JsonElement): JsonElement { - require(element is JsonArray) // this serializer is used only with lists - return element.singleOrNull() ?: element - } -``` - - - -Now, if you serialize a single-element list of objects from Kotlin: - -```kotlin -fun main() { - val data = Project("kotlinx.serialization", listOf(User("kotlin"))) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-26.kt). - -You end up with a single JSON object, not an array with one element: - -```text -{"name":"kotlinx.serialization","users":{"name":"kotlin"}} -``` - - - -### Manipulating default values - -Another kind of useful transformation is omitting specific values from the output JSON, for example, if it -is used as default when missing or for other reasons. - -Imagine that you cannot specify a default value for the `language` property in the `Project` data model for some reason, -but you need it omitted from the JSON when it is equal to `Kotlin` (we can all agree that Kotlin should be default anyway). -You can fix it by writing the special `ProjectSerializer` based on -the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) for the `Project` class. - -```kotlin -@Serializable -class Project(val name: String, val language: String) - -object ProjectSerializer : JsonTransformingSerializer(Project.serializer()) { - override fun transformSerialize(element: JsonElement): JsonElement = - // Filter out top-level key value pair with the key "language" and the value "Kotlin" - JsonObject(element.jsonObject.filterNot { - (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin" - }) -} -``` - -In the example below, we are serializing the `Project` class at the top-level, so we explicitly -pass the above `ProjectSerializer` to [Json.encodeToString] function as was shown in -the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: - -```kotlin -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) // using plugin-generated serializer - println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer -} -``` - -> You can get the full code [here](../../guide/example/example-json-27.kt). - -See the effect of the custom serializer: - -```text -{"name":"kotlinx.serialization","language":"Kotlin"} -{"name":"kotlinx.serialization"} -``` - - - -### Content-based polymorphic deserialization - -Typically, [polymorphic serialization](polymorphism.md) requires a dedicated `"type"` key -(also known as _class discriminator_) in the incoming JSON object to determine the actual serializer -which should be used to deserialize Kotlin class. - -However, sometimes the `type` property may not be present in the input. In this case, you need to guess -the actual type by the shape of JSON, for example by the presence of a specific key. - -[JsonContentPolymorphicSerializer] provides a skeleton implementation for such a strategy. -To use it, override its `selectDeserializer` method. -Let's start with the following class hierarchy. - -> Note that is does not have to be `sealed` as recommended in the [Sealed classes](polymorphism.md#sealed-classes) section, -> because we are not going to take advantage of the plugin-generated code that automatically selects the -> appropriate subclass, but are going to implement this code manually. - - - -```kotlin -@Serializable -abstract class Project { - abstract val name: String -} - -@Serializable -data class BasicProject(override val name: String): Project() - - -@Serializable -data class OwnedProject(override val name: String, val owner: String) : Project() -``` - -You can distinguish the `BasicProject` and `OwnedProject` subclasses by the presence of -the `owner` key in the JSON object. - -```kotlin -object ProjectSerializer : JsonContentPolymorphicSerializer(Project::class) { - override fun selectDeserializer(element: JsonElement) = when { - "owner" in element.jsonObject -> OwnedProject.serializer() - else -> BasicProject.serializer() - } -} -``` - -When you use this serializer to serialize data, either [registered](polymorphism.md#registered-subclasses) or -the default serializer is selected for the actual type at runtime: - -```kotlin -fun main() { - val data = listOf( - OwnedProject("kotlinx.serialization", "kotlin"), - BasicProject("example") - ) - val string = Json.encodeToString(ListSerializer(ProjectSerializer), data) - println(string) - println(Json.decodeFromString(ListSerializer(ProjectSerializer), string)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-28.kt). - -No class discriminator is added in the JSON output: - -```text -[{"name":"kotlinx.serialization","owner":"kotlin"},{"name":"example"}] -[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)] -``` - - - -### Under the hood (experimental) - -Although abstract serializers mentioned above can cover most of the cases, it is possible to implement similar machinery -manually, using only the [KSerializer] class. -If tweaking the abstract methods `transformSerialize`/`transformDeserialize`/`selectDeserializer` is not enough, -then altering `serialize`/`deserialize` is a way to go. - -Here are some useful things about custom serializers with [Json]: - -* [Encoder] can be cast to [JsonEncoder], and [Decoder] to [JsonDecoder], if the current format is [Json]. -* `JsonDecoder` has the [decodeJsonElement][JsonDecoder.decodeJsonElement] method and `JsonEncoder` - has the [encodeJsonElement][JsonEncoder.encodeJsonElement] method, - which basically retrieve an element from and insert an element to a current position in the stream. -* Both [`JsonDecoder`][JsonDecoder.json] and [`JsonEncoder`][JsonEncoder.json] have the `json` property, - which returns [Json] instance with all settings that are currently in use. -* [Json] has the [encodeToJsonElement][Json.encodeToJsonElement] and [decodeFromJsonElement][Json.decodeFromJsonElement] methods. - -Given all that, it is possible to implement two-stage conversion `Decoder -> JsonElement -> value` or -`value -> JsonElement -> Encoder`. -For example, you can implement a fully custom serializer for the following `Response` class so that its -`Ok` subclass is represented directly, but the `Error` subclass is represented by an object with the error message: - - - -```kotlin -@Serializable(with = ResponseSerializer::class) -sealed class Response { - data class Ok(val data: T) : Response() - data class Error(val message: String) : Response() -} - -class ResponseSerializer(private val dataSerializer: KSerializer) : KSerializer> { - override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) { - element("Ok", dataSerializer.descriptor) - element("Error", buildClassSerialDescriptor("Error") { - element("message") - }) - } - - override fun deserialize(decoder: Decoder): Response { - // Decoder -> JsonDecoder - require(decoder is JsonDecoder) // this class can be decoded only by Json - // JsonDecoder -> JsonElement - val element = decoder.decodeJsonElement() - // JsonElement -> value - if (element is JsonObject && "error" in element) - return Response.Error(element["error"]!!.jsonPrimitive.content) - return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element)) - } - - override fun serialize(encoder: Encoder, value: Response) { - // Encoder -> JsonEncoder - require(encoder is JsonEncoder) // This class can be encoded only by Json - // value -> JsonElement - val element = when (value) { - is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data) - is Response.Error -> buildJsonObject { put("error", value.message) } - } - // JsonElement -> JsonEncoder - encoder.encodeJsonElement(element) - } -} -``` - -Having this serializable `Response` implementation, you can take any serializable payload for its data -and serialize or deserialize the corresponding responses: - -```kotlin -@Serializable -data class Project(val name: String) - -fun main() { - val responses = listOf( - Response.Ok(Project("kotlinx.serialization")), - Response.Error("Not found") - ) - val string = Json.encodeToString(responses) - println(string) - println(Json.decodeFromString>>(string)) -} -``` - -> You can get the full code [here](../../guide/example/example-json-29.kt). - -This gives you fine-grained control on the representation of the `Response` class in the JSON output: - -```text -[{"name":"kotlinx.serialization"},{"error":"Not found"}] -[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)] -``` - - - -### Maintaining custom JSON attributes - -A good example of custom JSON-specific serializer would be a deserializer -that packs all unknown JSON properties into a dedicated field of `JsonObject` type. - -Let's add `UnknownProject` – a class with the `name` property and arbitrary details flattened into the same object: - - - -```kotlin -data class UnknownProject(val name: String, val details: JsonObject) -``` - -However, the default plugin-generated serializer requires details -to be a separate JSON object and that's not what we want. - -To mitigate that, write an own serializer that uses the fact that it works only with the `Json` format: - -```kotlin -object UnknownProjectSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") { - element("name") - element("details") - } - - override fun deserialize(decoder: Decoder): UnknownProject { - // Cast to JSON-specific interface - val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") - // Read the whole content as JSON - val json = jsonInput.decodeJsonElement().jsonObject - // Extract and remove name property - val name = json.getValue("name").jsonPrimitive.content - val details = json.toMutableMap() - details.remove("name") - return UnknownProject(name, JsonObject(details)) - } - - override fun serialize(encoder: Encoder, value: UnknownProject) { - error("Serialization is not supported") - } -} -``` - -Now it can be used to read flattened JSON details as `UnknownProject`: - -```kotlin -fun main() { - println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}""")) -} -``` - -> You can get the full code [here](../../guide/example/example-json-30.kt). - -```text -UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"}) -``` - - - --- The next chapter covers [Alternative and custom formats (experimental)](formats.md). @@ -1435,70 +26,10 @@ The next chapter covers [Alternative and custom formats (experimental)](formats. - -[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html -[InheritableSerialInfo]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-inheritable-serial-info/index.html -[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html -[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html - - -[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html -[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html - [Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html -[Json()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html -[JsonBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html -[JsonBuilder.prettyPrint]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/pretty-print.html -[JsonBuilder.isLenient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/is-lenient.html -[JsonBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html -[JsonNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html -[JsonBuilder.useAlternativeNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html -[JsonBuilder.encodeDefaults]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html -[JsonBuilder.explicitNulls]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html -[JsonBuilder.coerceInputValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html -[JsonBuilder.allowStructuredMapKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html -[JsonBuilder.allowSpecialFloatingPointValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html -[JsonBuilder.classDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html -[JsonClassDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/index.html -[JsonBuilder.classDiscriminatorMode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator-mode.html -[ClassDiscriminatorMode.NONE]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-n-o-n-e/index.html -[ClassDiscriminatorMode.POLYMORPHIC]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-p-o-l-y-m-o-r-p-h-i-c/index.html -[ClassDiscriminatorMode.ALL_JSON_OBJECTS]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-a-l-l_-j-s-o-n_-o-b-j-e-c-t-s/index.html -[JsonBuilder.decodeEnumsCaseInsensitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html -[JsonBuilder.namingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/naming-strategy.html -[JsonNamingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/index.html -[JsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/index.html -[Json.parseToJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html -[JsonPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/index.html -[JsonPrimitive.content]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/content.html -[JsonPrimitive()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html -[JsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-array/index.html -[JsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/index.html -[_jsonPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-primitive.html -[_jsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-array.html -[_jsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-object.html -[int]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int.html -[intOrNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int-or-null.html -[long]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long.html -[longOrNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long-or-null.html -[buildJsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-array.html -[buildJsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html -[Json.decodeFromJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html -[JsonUnquotedLiteral]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html -[JsonNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-null/index.html -[JsonTransformingSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/index.html -[Json.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html -[JsonContentPolymorphicSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/index.html -[JsonEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/index.html -[JsonDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/index.html -[JsonDecoder.decodeJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html -[JsonEncoder.encodeJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html -[JsonDecoder.json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/json.html -[JsonEncoder.json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/json.html -[Json.encodeToJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html diff --git a/docs/topics/knit.properties b/docs/topics/knit.properties index 2915aa39f7..9f0c445aba 100644 --- a/docs/topics/knit.properties +++ b/docs/topics/knit.properties @@ -1,2 +1,2 @@ -knit.dir=../guide/example/ -test.dir=../guide/test/ +knit.dir=../../guide/example/ +test.dir=../../guide/test/ diff --git a/docs/topics/serialization-customization-options.md b/docs/topics/serialization-customization-options.md index 9a9fe4635b..0ad5da3d89 100644 --- a/docs/topics/serialization-customization-options.md +++ b/docs/topics/serialization-customization-options.md @@ -1,3 +1,4 @@ + [//]: # (title: Serializable classes) The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. @@ -16,6 +17,11 @@ allowing classes to be easily converted to and from formats like JSON. In Kotlin, only properties with backing fields are serialized. This means that properties defined solely by getter/setter methods or delegated properties without backing fields are excluded from serialization: + + ```kotlin @Serializable class Project( @@ -41,6 +47,16 @@ fun main() { } ``` + + + + + + Kotlin Serialization natively supports nullable properties. Like [other defaults](#set-default-values-for-optional-properties), `null` values are not encoded in JSON: @@ -56,6 +72,16 @@ fun main() { } ``` + + + + + + Additionally, the type safety of Kotlin is strongly enforced. If a `null` value is encountered in a JSON object for a non-nullable Kotlin property, even if the property has a default value, an exception is raised: @@ -73,6 +99,17 @@ fun main() { } ``` + + + + + + > If you need to handle `null` values from third-party JSON, you can [coerce them to a default value](json.md#coercing-input-values). > {type="tip"} @@ -100,6 +137,17 @@ fun main() { } ``` + + + + + + > This behavior is intended to improve performance. > Avoid relying on any side effects in the initializer, as they will be bypassed if the initializer is not called. > @@ -128,8 +176,18 @@ fun main() { } ``` + + + + + + > If you need to reference non-serializable classes, you can mark them as [transient properties](#exclude-properties-with-the-transient-annotation), or -> provide a [custom serializer](serializers.md)for them. +> provide a [custom serializer](serializers.md) for them. > {type="tip"} @@ -155,6 +213,16 @@ fun main() { } ``` + + + + + + > If you attempt to serialize a circular structure, it will result in stack overflow. > You can use the [Transient properties](#exclude-properties-with-the-transient-annotation) to exclude some references from serialization. > @@ -185,6 +253,16 @@ fun main() { } ``` + + + + + + The type that is serialized to JSON depends on the actual compile-time type parameter specified for `Box`. If the generic type is not serializable, a compile-time error will occur, preventing the code from compiling. @@ -198,7 +276,7 @@ This section covers techniques for customizing property names, managing default By default, the property names in the serialization output, such as JSON, match their names in the source code. These names, known as _serial names_, can be customized -using the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation: +using the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation. For example, you can customize a property’s serial name to be shorter or more descriptive in the serialized output: ```kotlin @@ -214,6 +292,16 @@ fun main() { } ``` + + + + + + ### Define constructor properties for serialization The `@Serializable` annotation requires all parameters of the class's primary constructor to be properties. @@ -240,6 +328,16 @@ fun main() { } ``` + + + + + + ### Validate data in primary constructor When you need to validate a constructor parameter before storing it in a property, @@ -264,6 +362,16 @@ fun main() { } ``` + + + + + + ### Set default values for optional properties In Kotlin, an object can only be deserialized when all its properties are present in the input. @@ -286,6 +394,16 @@ fun main() { } ``` + + + + + + ### Make properties required with the @Required annotation A property with a default value can be made required in a serialized format with the [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) annotation. @@ -310,6 +428,16 @@ fun main() { } ``` + + + + + + ### Exclude properties with the @Transient annotation A property can be excluded from serialization by marking it with the [`@Transient`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/) annotation @@ -338,6 +466,17 @@ fun main() { } ``` + + + + + + > You can avoid exceptions from unknown keys in JSON, including those marked with the @Transient annotation, with the [`ignoreUnknownKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html) setting. > For more information, see the [Ignoring Unknown Keys](json.md#ignoring-unknown-keys) section. > @@ -360,6 +499,16 @@ fun main() { } ``` + + + + + + > You can learn more about how this behavior can be configured in the JSON format in the [Encoding defaults](json.md#encoding-defaults) section. > {type="tip"} @@ -397,3 +546,14 @@ fun main() { // {"name":"Bob"} } ``` + + + + + + diff --git a/docs/topics/serialization-guide.md b/docs/topics/serialization-guide.md index ff92bd44bc..959996a2f1 100644 --- a/docs/topics/serialization-guide.md +++ b/docs/topics/serialization-guide.md @@ -109,36 +109,6 @@ Once the project is set up, we can start serializing some classes. **Chapter 5.** [JSON Features](json.md) -* [Json configuration](json.md#json-configuration) - * [Pretty printing](json.md#pretty-printing) - * [Lenient parsing](json.md#lenient-parsing) - * [Ignoring unknown keys](json.md#ignoring-unknown-keys) - * [Alternative Json names](json.md#alternative-json-names) - * [Encoding defaults](json.md#encoding-defaults) - * [Explicit nulls](json.md#explicit-nulls) - * [Coercing input values](json.md#coercing-input-values) - * [Allowing structured map keys](json.md#allowing-structured-map-keys) - * [Allowing special floating-point values](json.md#allowing-special-floating-point-values) - * [Class discriminator for polymorphism](json.md#class-discriminator-for-polymorphism) - * [Class discriminator output mode](json.md#class-discriminator-output-mode) - * [Decoding enums in a case-insensitive manner](json.md#decoding-enums-in-a-case-insensitive-manner) - * [Global naming strategy](json.md#global-naming-strategy) - * [Base64](json.md#base64) -* [Json elements](json.md#json-elements) - * [Parsing to Json element](json.md#parsing-to-json-element) - * [Types of Json elements](json.md#types-of-json-elements) - * [Json element builders](json.md#json-element-builders) - * [Decoding Json elements](json.md#decoding-json-elements) - * [Encoding literal Json content (experimental)](json.md#encoding-literal-json-content-experimental) - * [Serializing large decimal numbers](json.md#serializing-large-decimal-numbers) - * [Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden](json.md#using-jsonunquotedliteral-to-create-a-literal-unquoted-value-of-null-is-forbidden) -* [Json transformations](json.md#json-transformations) - * [Array wrapping](json.md#array-wrapping) - * [Array unwrapping](json.md#array-unwrapping) - * [Manipulating default values](json.md#manipulating-default-values) - * [Content-based polymorphic deserialization](json.md#content-based-polymorphic-deserialization) - * [Under the hood (experimental)](json.md#under-the-hood-experimental) - * [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attributes) **Chapter 6.** [Alternative and custom formats (experimental)](formats.md) diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md index 783e90705f..bfb73888ad 100644 --- a/docs/topics/serialization-json-configuration.md +++ b/docs/topics/serialization-json-configuration.md @@ -1 +1,692 @@ -[//]: # (title: Customize JSON serialization settings) \ No newline at end of file +[//]: # (title: Customize JSON serialization) + +The default [`Json`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/) class enforces Kotlin type safety and only allows values that can be serialized into standard JSON. +However, you can handle non-standard JSON features by creating a _custom JSON format_. + +To create a custom JSON format, use the [`Json()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html) builder function. +You can base it on an existing `Json` instance, such as the default `Json` object, and specify the desired configuration +using the [`JsonBuilder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/) DSL. +The resulting `Json` instance is immutable and thread-safe, making it safe to store in a top-level property: + +```kotlin +// Configures a Json instance to ignore unknown keys +val customJson = Json { + ignoreUnknownKeys = true +} + +// The customJson instance can now be used like the default one +val jsonString = customJson.encodeToString(user) +println(jsonString) +``` + +> Reusing custom `Json` instances improves performance by allowing them to cache class-specific information. +> +{type="tip"} + +The following sections cover the various configuration features supported by `Json`. + + + +## Pretty printing + +By default, the `Json` output is a single line. You can configure it to pretty-print the output, adding indentations +and line breaks for better readability, by setting the [`prettyPrint`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/pretty-print.html) property to `true`: + + + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Creates a custom Json format +val format = Json { prettyPrint = true } + +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + + // Prints the pretty-printed JSON string + println(format.encodeToString(data)) +} +``` + + + +It produces the following result: + +```text +{ + "name": "kotlinx.serialization", + "language": "Kotlin" +} +``` + + + +## Lenient parsing + + + +By default, the `Json` parser enforces strict JSON rules to ensure compliance with the [RFC-4627](https://www.ietf.org/rfc/rfc4627.txt) specification. +These rules require keys and string literals to be quoted. + +To relax these restrictions, set the [`isLenient`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/is-lenient.html) property to `true`. +This allows the parser to handle more freely formatted data: + +```kotlin +val format = Json { isLenient = true } + +enum class Status { SUPPORTED } + +@Serializable +data class Project(val name: String, val status: Status, val votes: Int) + +fun main() { + // Decodes a JSON string with lenient parsing + // Lenient parsing allows unquoted keys, string and enum values, and quoted integers + val data = format.decodeFromString(""" + { + name : kotlinx.serialization, + status : SUPPORTED, + votes : "9000" + } + """) + println(data) + // Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000) +} +``` + + + + + + + +## Ignore unknown keys + +The JSON format is often used to process data from third-party services or other dynamic environments where new properties may be added over time. +By default, unknown keys encountered during deserialization cause an error. +You can prevent these errors by setting the [`ignoreUnknownKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html) property +to `true`, which ignores any unknown keys during deserialization: + +```kotlin +// Configures a Json instance to ignore unknown keys +val format = Json { ignoreUnknownKeys = true } + +@Serializable +data class Project(val name: String) + +fun main() { + val data = format.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + // Decodes the object even though the `Project` class doesn't have the `language` property + println(data) + // Project(name=kotlinx.serialization) +} +``` + + + + + + + +## Handle multiple JSON field names with @JsonNames + +When JSON fields are renamed due to schema version changes, +you can use the [`@SerialName`](serialization-customization-options.md#customize-serial-names) annotation to change the name of a JSON field. +However, this approach prevents decoding data with the old name. +To support multiple JSON names for a single Kotlin property, use the [`@JsonNames`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/) annotation: + +```kotlin +@Serializable +// Maps both "name" and "title" JSON fields to the `name` property +data class Project(@JsonNames("title") val name: String) + +fun main() { + val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") + println(project) + // Project(name=kotlinx.serialization) + + val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") + // Both `name` and `title` Json fields correspond to `name` property + println(oldProject) + // Project(name=kotlinx.coroutines) +} +``` + + + + + +> The `@JsonNames` annotation is enabled by the [`useAlternativeNames`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html) property in [`JsonBuilder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/). +> This property is set to `true` by default and allows Json to recognize and handle multiple names for a single property. +> If you are not using `@JsonNames` and want to optimize performance, +> especially when skipping many unknown fields with `ignoreUnknownKeys`, you can set this property to `false`. +> +{type="note"} + + + +## Encode default values + +By default, the JSON serializer does not encode default property values because they are automatically assigned to missing fields during decoding. +This behavior is especially useful for nullable properties with null defaults, as it avoids writing unnecessary `null` values. +For more details, see the [Manage serialization of default properties](serialization-customization-options.md#manage-serialization-of-default-properties-with-encodeddefault) section. + +You can change this default behavior by setting the [`encodeDefaults`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html) property to `true`: + +```kotlin +// Configures a Json instance to encode default values +val format = Json { encodeDefaults = true } + +@Serializable +class Project( + val name: String, + val language: String = "Kotlin", + val website: String? = null +) + +fun main() { + val data = Project("kotlinx.serialization") + + // Encodes all the property values including the default ones + println(format.encodeToString(data)) + // {"name":"kotlinx.serialization","language":"Kotlin","website":null} +} +``` + + + + + + + +## Omit explicit nulls + +By default, all `null` values are encoded into JSON strings. +To omit `null` values, set the [`explicitNulls`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html) property to `false`: + +```kotlin +// Configures a Json instance to omit null values during serialization +val format = Json { explicitNulls = false } + +@Serializable +data class Project( + val name: String, + val language: String, + val version: String? = "1.2.2", + val website: String?, + val description: String? = null +) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin", null, null, null) + val json = format.encodeToString(data) + + // The version, website, and description fields are omitted from the output JSON + println(json) + // {"name":"kotlinx.serialization","language":"Kotlin"} + + // Missing nullable fields without defaults are treated as null + // Fields with defaults are filled with their default values + println(format.decodeFromString(json)) + // Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null) +} +``` + +Encoding and decoding can become asymmetrical when `explicitNulls` is set to `false`. +In the example above, the `version` field is `null` before encoding but decodes to `1.2.2`. + + + + + +> You can configure the decoder to handle certain invalid input values by treating them as missing fields using the [`coerceInputValues`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html) property. +> For more information, see the [Coerce input values](#coerce-input-values) section. +> +{type="tip"} + + + +## Coerce input values + +When working with JSON data from third parties, the format can evolve over time, leading to changes in field types. +This can lead to exceptions during decoding when the actual values do not match the expected types. +The default `Json` implementation is strict about input types, as demonstrated in +the [@Serializable annotation](serialization-customization-options.md#the-serializable-annotation) section. +You can relax this restriction using the [coerceInputValues](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html) property. + +This property only affects decoding. It treats certain invalid input values as if the corresponding property were missing. +The current supported invalid values are: + +* `null` inputs for non-nullable types +* unknown values for enums + +> This list may be expanded in the future, making `Json` instances with this property even more permissive +> by replacing invalid values with defaults or `null`. +> +{type="note"} + +If value is missing, it is replaced with a default property value if it exists. +For enums, if no default is defined and the [`explicitNulls`]((#omit-explicit-nulls)) property is set to `false`, +the value is replaced with `null` if the property is nullable: + +```kotlin +val format = Json { coerceInputValues = true } + +@Serializable +data class Project(val name: String, val language: String = "Kotlin") + +fun main() { + val data = format.decodeFromString(""" + {"name":"kotlinx.serialization","language":null} + """) + + // The invalid `null` value for `language` is coerced to its default value + println(data) + // Project(name=kotlinx.serialization, language=Kotlin) +} +``` + + + + + + + +You can use the `coerceInputValues` property together with the `explicitNulls` property to handle invalid enum values: + +```kotlin +enum class Color { BLACK, WHITE } + +@Serializable +data class Brush(val foreground: Color = Color.BLACK, val background: Color?) + +val json = Json { + coerceInputValues = true + explicitNulls = false +} + +fun main() { + + // Decodes `foreground` to its default value and `background` to `null` + val brush = json.decodeFromString("""{"foreground":"pink", "background":"purple"}""") + println(brush) + // Brush(foreground=BLACK, background=null) +} +``` + + + + + + + +## Encode structured map keys + +The JSON format does not natively support maps with structured keys, as JSON keys are typically strings representing only primitives or enums. +You can use the [`allowStructuredMapKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html) property to serialize maps with user-defined class keys: + +```kotlin +val format = Json { allowStructuredMapKeys = true } + +@Serializable +data class Project(val name: String) + +fun main() { + val map = mapOf( + Project("kotlinx.serialization") to "Serialization", + Project("kotlinx.coroutines") to "Coroutines" + ) + // Serializes the map with structured keys as a JSON array: + // `[key1, value1, key2, value2,...]`. + println(format.encodeToString(map)) + // [{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"] +} +``` + + + + + + + +## Encode special floating-point values + +By default, special floating-point values like [`Double.NaN`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html) +and infinities are not supported in JSON because the JSON specification does not permit them. +You can allow their encoding using the [allowSpecialFloatingPointValues](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html) +property: + +```kotlin +// Configures a Json instance to allow special floating-point values +val format = Json { allowSpecialFloatingPointValues = true } + +@Serializable +class Data( + val value: Double +) + +fun main() { + val data = Data(Double.NaN) + // This example produces the following non-standard JSON output, yet it is a widely used encoding for + // special values in JVM world: + println(format.encodeToString(data)) + // {"value":NaN} +} +``` + + + + + + + +## Specify class discriminator for polymorphism + +When working with polymorphic data, you can use the +[`classDiscriminator`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html) property to specify a key name that indicates the type of the polymorphic object being serialized. +In combination with an [explicitly specified serial name using the `@SerialName` annotation](serialization-customization-options.md#customize-serial-names), +this approach provides full control over the resulting JSON structure: + +```kotlin +// Configures a Json instance to use a custom class discriminator +val format = Json { classDiscriminator = "#class" } + +@Serializable +sealed class Project { + abstract val name: String +} + +// Specifies a custom serial name for the OwnedProject class +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project() + +// Specifies a custom serial name for the SimpleProject class +@Serializable +@SerialName("simple") +class SimpleProject(override val name: String) : Project() + +fun main() { + val simpleProject: Project = SimpleProject("kotlinx.serialization") + val ownedProject: Project = OwnedProject("kotlinx.coroutines", "kotlin") + + // Serializes SimpleProject with #class: "simple" + println(format.encodeToString(simpleProject)) + // {"#class":"simple","name":"kotlinx.serialization"} + + // Serializes OwnedProject with #class: "owned" + println(format.encodeToString(ownedProject)) + // {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} +``` + + + + + + + +While the `classDiscriminator` property in a `Json` instance allows you to specify a single discriminator key for all polymorphic types, the [`@JsonClassDiscriminator`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/) annotation provides greater flexibility. +It allows you to define a custom discriminator directly on the base class, which is automatically inherited by all its subclasses. +This behavior is enabled by the [`@InheritableSerialInfo`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-inheritable-serial-info/) meta-annotation: + +```kotlin +// The @JsonClassDiscriminator annotation is inheritable, so all subclasses of `Base` will have the same discriminator +@Serializable +@JsonClassDiscriminator("message_type") +sealed class Base + +// Class discriminator is inherited from Base +@Serializable +sealed class ErrorClass: Base() + +// Defines a class that combines a message and an optional error +@Serializable +data class Message(val message: Base, val error: ErrorClass?) + +@Serializable +@SerialName("my.app.BaseMessage") +data class BaseMessage(val message: String) : Base() + +@Serializable +@SerialName("my.app.GenericError") +data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() + +val format = Json { classDiscriminator = "#class" } + +fun main() { + val data = Message(BaseMessage("not found"), GenericError(404)) + // The discriminator from the `Base` class is used + println(format.encodeToString(data)) + // {"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}} +} +``` + +> It is not possible to specify different class discriminators explicitly within subclasses of a sealed base class. +> Only hierarchies with distinct, non-overlapping subclasses can have different discriminators. +> The discriminator specified in the `@JsonClassDiscriminator` annotation takes priority over any discriminator set in the `Json` configuration. +> +{type="note"} + + + + + + + +### Set class discriminator output mode + +You can use the [`JsonBuilder.classDiscriminatorMode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator-mode.html) property to control how class discriminators are added to your JSON output. +As described in the [Specify class discriminator for polymorphism](#specify-class-discriminator-for-polymorphism) section, the default behavior adds the discriminator only for polymorphic types, which is useful when working with [polymorphic class hierarchies](polymorphism.md#sealed-classes). +Depending on your specific requirements, you can choose from the following [`ClassDiscriminatorMode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/) options: + +* [`POLYMORPHIC`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-p-o-l-y-m-o-r-p-h-i-c/): Adds the class discriminator only for polymorphic types. This is the default behavior. +* [`ALL_JSON_OBJECTS`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-a-l-l_-j-s-o-n_-o-b-j-e-c-t-s/): Adds the class discriminator to every JSON object, wherever possible. +* [`NONE`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-n-o-n-e/): Omits the class discriminator in the output. + + +For example, setting the `ClassDiscriminatorMode` to `NONE` produces an output without a discriminator: + +```kotlin +// Configures a Json instance to omit the class discriminator from the output +val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } + +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes without a discriminator + println(format.encodeToString(data)) + // {"name":"kotlinx.coroutines","owner":"kotlin"} +} +``` + +> Without the discriminator, `kotlinx.serialization` cannot deserialize this output back into the appropriate type. +> +{type="note"} + + + + + + + +## Decode enums in a case-insensitive manner + +[Kotlin's naming policy recommends](https://kotlinlang.org/docs/coding-conventions.html#property-names) naming enum values +using either uppercase underscore-separated names or upper camel case names. +[Json] uses exact Kotlin enum values names for decoding by default. +However, sometimes third-party JSONs have such values named in lowercase or some mixed case. +In this case, it is possible to decode enum values in a case-insensitive manner using [JsonBuilder.decodeEnumsCaseInsensitive] property: + +```kotlin +val format = Json { decodeEnumsCaseInsensitive = true } + +enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } + +@Serializable +data class CasesList(val cases: List) + +fun main() { + println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) +} +``` + + + +It affects serial names as well as alternative names specified with [JsonNames] annotation, so both values are successfully decoded: + + + +This property does not affect encoding in any way. + + + +## Global naming strategy + +If properties' names in Json input are different from Kotlin ones, it is recommended to specify the name +for each property explicitly using [`@SerialName` annotation](basic-serialization.md#serial-field-names). +However, there are certain situations where transformation should be applied to every serial name — such as migration +from other frameworks or legacy codebase. For that cases, it is possible to specify a [namingStrategy][JsonBuilder.namingStrategy] +for a [Json] instance. `kotlinx.serialization` provides one strategy implementation out of the box, the [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html): + +```kotlin +@Serializable +data class Project(val projectName: String, val projectOwner: String) + +val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase } + +fun main() { + val project = format.decodeFromString("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""") + println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) +} +``` + + + +As you can see, both serialization and deserialization work as if all serial names are transformed from camel case to snake case: + + + +There are some caveats one should remember while dealing with a [JsonNamingStrategy]: + +* Due to the nature of the `kotlinx.serialization` framework, naming strategy transformation is applied to all properties regardless + of whether their serial name was taken from the property name or provided by [SerialName] annotation. + Effectively, it means one cannot avoid transformation by explicitly specifying the serial name. To be able to deserialize + non-transformed names, [JsonNames] annotation can be used instead. + +* Collision of the transformed name with any other (transformed) properties serial names or any alternative names + specified with [JsonNames] will lead to a deserialization exception. + +* Global naming strategies are very implicit: by looking only at the definition of the class, + it is impossible to determine which names it will have in the serialized form. + As a consequence, naming strategies are not friendly to actions like Find Usages/Rename in IDE, full-text search by grep, etc. + For them, the original name and the transformed are two different things; + changing one without the other may introduce bugs in many unexpected ways and lead to greater maintenance efforts for code with global naming strategies. + +Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application. + + + + + +[RFC-4627]: https://www.ietf.org/rfc/rfc4627.txt +[BigDecimal]: https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html + + +[Double]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/ +[Double.NaN]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html +[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/ +[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/ + + + + +[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html + + + + + +[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html +[JsonBuilder.decodeEnumsCaseInsensitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html +[JsonNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html +[JsonBuilder.namingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/naming-strategy.html +[JsonNamingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/index.html + + diff --git a/docs/topics/serialization-json-elements.md b/docs/topics/serialization-json-elements.md index f49627b287..35006a9ac4 100644 --- a/docs/topics/serialization-json-elements.md +++ b/docs/topics/serialization-json-elements.md @@ -1 +1,299 @@ -[//]: # (title: Managing JSON elements) \ No newline at end of file + +[//]: # (title: Managing JSON elements) + +Aside from direct conversions between strings and JSON objects, Kotlin serialization offers APIs that allow +other ways of working with JSON in the code. For example, you might need to tweak the data before it can parse +or otherwise work with such an unstructured data that it does not readily fit into the typesafe world of Kotlin +serialization. + +The main concept in this part of the library is [JsonElement]. Read on to learn what you can do with it. + +### Parsing to Json element + +A string can be _parsed_ into an instance of [JsonElement] with the [Json.parseToJsonElement] function. +It is called neither decoding nor deserialization because none of that happens in the process. +It just parses a JSON and forms an object representing it: + + + +```kotlin +fun main() { + val element = Json.parseToJsonElement(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(element) +} +``` + + + +A `JsonElement` prints itself as a valid JSON: + + + + + +### Types of Json elements + +A [JsonElement] class has three direct subtypes, closely following JSON grammar: + +* [JsonPrimitive] represents primitive JSON elements, such as string, number, boolean, and null. + Each primitive has a simple string [content][JsonPrimitive.content]. There is also a + [JsonPrimitive()] constructor function overloaded to accept various primitive Kotlin types and + to convert them to `JsonPrimitive`. + +* [JsonArray] represents a JSON `[...]` array. It is a Kotlin [List] of `JsonElement` items. + +* [JsonObject] represents a JSON `{...}` object. It is a Kotlin [Map] from `String` keys to `JsonElement` values. + +The `JsonElement` class has extensions that cast it to its corresponding subtypes: +[jsonPrimitive][_jsonPrimitive], [jsonArray][_jsonArray], [jsonObject][_jsonObject]. The `JsonPrimitive` class, +in turn, provides converters to Kotlin primitive types: [int], [intOrNull], [long], [longOrNull], +and similar ones for other types. This is how you can use them for processing JSON whose structure you know: + +```kotlin +fun main() { + val element = Json.parseToJsonElement(""" + { + "name": "kotlinx.serialization", + "forks": [{"votes": 42}, {"votes": 9000}, {}] + } + """) + val sum = element + .jsonObject["forks"]!! + .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 } + println(sum) +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-02.kt). + +The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`: + +```text +9042 +``` + + + +Note that the execution will fail if the structure of the data is otherwise different. + +### Json element builders + +You can construct instances of specific [JsonElement] subtypes using the respective builder functions +[buildJsonArray] and [buildJsonObject]. They provide a DSL to define the resulting JSON structure. It +is similar to Kotlin standard library collection builders, but with a JSON-specific convenience +of more type-specific overloads and inner builder functions. The following example shows +all the key features: + +```kotlin +fun main() { + val element = buildJsonObject { + put("name", "kotlinx.serialization") + putJsonObject("owner") { + put("name", "kotlin") + } + putJsonArray("forks") { + addJsonObject { + put("votes", 42) + } + addJsonObject { + put("votes", 9000) + } + } + } + println(element) +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-03.kt). + +As a result, you get a proper JSON string: + +```text +{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} +``` + + + +### Decoding Json elements + +An instance of the [JsonElement] class can be decoded into a serializable object using +the [Json.decodeFromJsonElement] function: + +```kotlin +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val element = buildJsonObject { + put("name", "kotlinx.serialization") + put("language", "Kotlin") + } + val data = Json.decodeFromJsonElement(element) + println(data) +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-04.kt). + +The result is exactly what you would expect: + +```text +Project(name=kotlinx.serialization, language=Kotlin) +``` + + + +### Encoding literal Json content (experimental) + +> This functionality is experimental and requires opting-in to [the experimental Kotlinx Serialization API](compatibility.md#experimental-api). + +In some cases it might be necessary to encode an arbitrary unquoted value. +This can be achieved with [JsonUnquotedLiteral]. + +#### Serializing large decimal numbers + +The JSON specification does not restrict the size or precision of numbers, however it is not possible to serialize +numbers of arbitrary size or precision using [JsonPrimitive()]. + +If [Double] is used, then the numbers are limited in precision, meaning that large numbers are truncated. +When using Kotlin/JVM [BigDecimal] can be used instead, but [JsonPrimitive()] will encode the value as a string, not a +number. + +```kotlin +import java.math.BigDecimal + +val format = Json { prettyPrint = true } + +fun main() { + val pi = BigDecimal("3.141592653589793238462643383279") + + val piJsonDouble = JsonPrimitive(pi.toDouble()) + val piJsonString = JsonPrimitive(pi.toString()) + + val piObject = buildJsonObject { + put("pi_double", piJsonDouble) + put("pi_string", piJsonString) + } + + println(format.encodeToString(piObject)) +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-05.kt). + +Even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. +The [Double] value is truncated to 15 decimal places, and the String is wrapped in quotes - which is not a JSON number. + +```text +{ + "pi_double": 3.141592653589793, + "pi_string": "3.141592653589793238462643383279" +} +``` + + + +To avoid precision loss, the string value of `pi` can be encoded using [JsonUnquotedLiteral]. + +```kotlin +import java.math.BigDecimal + +val format = Json { prettyPrint = true } + +fun main() { + val pi = BigDecimal("3.141592653589793238462643383279") + + // use JsonUnquotedLiteral to encode raw JSON content + val piJsonLiteral = JsonUnquotedLiteral(pi.toString()) + + val piJsonDouble = JsonPrimitive(pi.toDouble()) + val piJsonString = JsonPrimitive(pi.toString()) + + val piObject = buildJsonObject { + put("pi_literal", piJsonLiteral) + put("pi_double", piJsonDouble) + put("pi_string", piJsonString) + } + + println(format.encodeToString(piObject)) +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-06.kt). + +`pi_literal` now accurately matches the value defined. + +```text +{ + "pi_literal": 3.141592653589793238462643383279, + "pi_double": 3.141592653589793, + "pi_string": "3.141592653589793238462643383279" +} +``` + + + +To decode `pi` back to a [BigDecimal], the string content of the [JsonPrimitive] can be used. + +(This demonstration uses a [JsonPrimitive] for simplicity. For a more re-usable method of handling serialization, see +[Json Transformations](#json-transformations) below.) + + +```kotlin +import java.math.BigDecimal + +fun main() { + val piObjectJson = """ + { + "pi_literal": 3.141592653589793238462643383279 + } + """.trimIndent() + + val piObject: JsonObject = Json.decodeFromString(piObjectJson) + + val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content + + val pi = BigDecimal(piJsonLiteral) + + println(pi) +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-07.kt). + +The exact value of `pi` is decoded, with all 30 decimal places of precision that were in the source JSON. + +```text +3.141592653589793238462643383279 +``` + + + +#### Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden + +To avoid creating an inconsistent state, encoding a String equal to `"null"` is forbidden. +Use [JsonNull] or [JsonPrimitive] instead. + +```kotlin +fun main() { + // caution: creating null with JsonUnquotedLiteral will cause an exception! + JsonUnquotedLiteral("null") +} +``` + +> You can get the full code [here](../../guide/example/example-json-elements-08.kt). + +```text +Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive +``` + + diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md index 0eaae8a8e5..67ace93fc9 100644 --- a/docs/topics/serialization-transform-json.md +++ b/docs/topics/serialization-transform-json.md @@ -1 +1,408 @@ -[//]: # (title: Transform JSON during serialiazation and deserialization) + +[//]: # (title: Transform JSON during serialization and deserialization) + +To affect the shape and contents of JSON output after serialization, or adapt input to deserialization, +it is possible to write a [custom serializer](serializers.md). However, it may be inconvenient to +carefully follow [Encoder] and [Decoder] calling conventions, especially for relatively small and easy tasks. +For that purpose, Kotlin serialization provides an API that can reduce the burden of implementing a custom +serializer to a problem of manipulating a Json elements tree. + +We recommend that you get familiar with the [Serializers](serializers.md) chapter: among other things, it +explains how custom serializers are bound to classes. + +Transformation capabilities are provided by the abstract [JsonTransformingSerializer] class which implements [KSerializer]. +Instead of direct interaction with `Encoder` or `Decoder`, this class asks you to supply transformations for JSON tree +represented by the [JsonElement] class using the`transformSerialize` and +`transformDeserialize` methods. Let's take a look at the examples. + +### Array wrapping + +The first example is an implementation of JSON array wrapping for lists. + +Consider a REST API that returns a JSON array of `User` objects, or a single object (not wrapped into an array) if there +is only one element in the result. + +In the data model, use the [`@Serializable`][Serializable] annotation to specify a custom serializer for a +`users: List` property. + + + +```kotlin +@Serializable +data class Project( + val name: String, + @Serializable(with = UserListSerializer::class) + val users: List +) + +@Serializable +data class User(val name: String) +``` + +Since this example covers only the deserialization case, you can implement `UserListSerializer` and override only the +`transformDeserialize` function. The `JsonTransformingSerializer` constructor takes an original serializer +as parameter (this approach is shown in the section [Constructing collection serializers](serializers.md#constructing-collection-serializers)): + +```kotlin +object UserListSerializer : JsonTransformingSerializer>(ListSerializer(User.serializer())) { + // If response is not an array, then it is a single object that should be wrapped into the array + override fun transformDeserialize(element: JsonElement): JsonElement = + if (element !is JsonArray) JsonArray(listOf(element)) else element +} +``` + +Now you can test the code with a JSON array or a single JSON object as inputs. + +```kotlin +fun main() { + println(Json.decodeFromString(""" + {"name":"kotlinx.serialization","users":{"name":"kotlin"}} + """)) + println(Json.decodeFromString(""" + {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]} + """)) +} +``` + +> You can get the full code [here](../../guide/example/example-json-transform-01.kt). + +The output shows that both cases are correctly deserialized into a Kotlin [List]. + +```text +Project(name=kotlinx.serialization, users=[User(name=kotlin)]) +Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) +``` + + + +### Array unwrapping + +You can also implement the `transformSerialize` function to unwrap a single-element list into a single JSON object +during serialization: + + + +```kotlin + override fun transformSerialize(element: JsonElement): JsonElement { + require(element is JsonArray) // this serializer is used only with lists + return element.singleOrNull() ?: element + } +``` + + + +Now, if you serialize a single-element list of objects from Kotlin: + +```kotlin +fun main() { + val data = Project("kotlinx.serialization", listOf(User("kotlin"))) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-json-transform-02.kt). + +You end up with a single JSON object, not an array with one element: + +```text +{"name":"kotlinx.serialization","users":{"name":"kotlin"}} +``` + + + +### Manipulating default values + +Another kind of useful transformation is omitting specific values from the output JSON, for example, if it +is used as default when missing or for other reasons. + +Imagine that you cannot specify a default value for the `language` property in the `Project` data model for some reason, +but you need it omitted from the JSON when it is equal to `Kotlin` (we can all agree that Kotlin should be default anyway). +You can fix it by writing the special `ProjectSerializer` based on +the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) for the `Project` class. + +```kotlin +@Serializable +class Project(val name: String, val language: String) + +object ProjectSerializer : JsonTransformingSerializer(Project.serializer()) { + override fun transformSerialize(element: JsonElement): JsonElement = + // Filter out top-level key value pair with the key "language" and the value "Kotlin" + JsonObject(element.jsonObject.filterNot { + (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin" + }) +} +``` + +In the example below, we are serializing the `Project` class at the top-level, so we explicitly +pass the above `ProjectSerializer` to [Json.encodeToString] function as was shown in +the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: + +```kotlin +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(data)) // using plugin-generated serializer + println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer +} +``` + +> You can get the full code [here](../../guide/example/example-json-transform-03.kt). + +See the effect of the custom serializer: + +```text +{"name":"kotlinx.serialization","language":"Kotlin"} +{"name":"kotlinx.serialization"} +``` + + + +### Content-based polymorphic deserialization + +Typically, [polymorphic serialization](polymorphism.md) requires a dedicated `"type"` key +(also known as _class discriminator_) in the incoming JSON object to determine the actual serializer +which should be used to deserialize Kotlin class. + +However, sometimes the `type` property may not be present in the input. In this case, you need to guess +the actual type by the shape of JSON, for example by the presence of a specific key. + +[JsonContentPolymorphicSerializer] provides a skeleton implementation for such a strategy. +To use it, override its `selectDeserializer` method. +Let's start with the following class hierarchy. + +> Note that is does not have to be `sealed` as recommended in the [Sealed classes](polymorphism.md#sealed-classes) section, +> because we are not going to take advantage of the plugin-generated code that automatically selects the +> appropriate subclass, but are going to implement this code manually. + + + +```kotlin +@Serializable +abstract class Project { + abstract val name: String +} + +@Serializable +data class BasicProject(override val name: String): Project() + + +@Serializable +data class OwnedProject(override val name: String, val owner: String) : Project() +``` + +You can distinguish the `BasicProject` and `OwnedProject` subclasses by the presence of +the `owner` key in the JSON object. + +```kotlin +object ProjectSerializer : JsonContentPolymorphicSerializer(Project::class) { + override fun selectDeserializer(element: JsonElement) = when { + "owner" in element.jsonObject -> OwnedProject.serializer() + else -> BasicProject.serializer() + } +} +``` + +When you use this serializer to serialize data, either [registered](polymorphism.md#registered-subclasses) or +the default serializer is selected for the actual type at runtime: + +```kotlin +fun main() { + val data = listOf( + OwnedProject("kotlinx.serialization", "kotlin"), + BasicProject("example") + ) + val string = Json.encodeToString(ListSerializer(ProjectSerializer), data) + println(string) + println(Json.decodeFromString(ListSerializer(ProjectSerializer), string)) +} +``` + +> You can get the full code [here](../../guide/example/example-json-transform-04.kt). + +No class discriminator is added in the JSON output: + +```text +[{"name":"kotlinx.serialization","owner":"kotlin"},{"name":"example"}] +[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)] +``` + + + +### Under the hood (experimental) + +Although abstract serializers mentioned above can cover most of the cases, it is possible to implement similar machinery +manually, using only the [KSerializer] class. +If tweaking the abstract methods `transformSerialize`/`transformDeserialize`/`selectDeserializer` is not enough, +then altering `serialize`/`deserialize` is a way to go. + +Here are some useful things about custom serializers with [Json]: + +* [Encoder] can be cast to [JsonEncoder], and [Decoder] to [JsonDecoder], if the current format is [Json]. +* `JsonDecoder` has the [decodeJsonElement][JsonDecoder.decodeJsonElement] method and `JsonEncoder` + has the [encodeJsonElement][JsonEncoder.encodeJsonElement] method, + which basically retrieve an element from and insert an element to a current position in the stream. +* Both [`JsonDecoder`][JsonDecoder.json] and [`JsonEncoder`][JsonEncoder.json] have the `json` property, + which returns [Json] instance with all settings that are currently in use. +* [Json] has the [encodeToJsonElement][Json.encodeToJsonElement] and [decodeFromJsonElement][Json.decodeFromJsonElement] methods. + +Given all that, it is possible to implement two-stage conversion `Decoder -> JsonElement -> value` or +`value -> JsonElement -> Encoder`. +For example, you can implement a fully custom serializer for the following `Response` class so that its +`Ok` subclass is represented directly, but the `Error` subclass is represented by an object with the error message: + + + +```kotlin +@Serializable(with = ResponseSerializer::class) +sealed class Response { + data class Ok(val data: T) : Response() + data class Error(val message: String) : Response() +} + +class ResponseSerializer(private val dataSerializer: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) { + element("Ok", dataSerializer.descriptor) + element("Error", buildClassSerialDescriptor("Error") { + element("message") + }) + } + + override fun deserialize(decoder: Decoder): Response { + // Decoder -> JsonDecoder + require(decoder is JsonDecoder) // this class can be decoded only by Json + // JsonDecoder -> JsonElement + val element = decoder.decodeJsonElement() + // JsonElement -> value + if (element is JsonObject && "error" in element) + return Response.Error(element["error"]!!.jsonPrimitive.content) + return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element)) + } + + override fun serialize(encoder: Encoder, value: Response) { + // Encoder -> JsonEncoder + require(encoder is JsonEncoder) // This class can be encoded only by Json + // value -> JsonElement + val element = when (value) { + is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data) + is Response.Error -> buildJsonObject { put("error", value.message) } + } + // JsonElement -> JsonEncoder + encoder.encodeJsonElement(element) + } +} +``` + +Having this serializable `Response` implementation, you can take any serializable payload for its data +and serialize or deserialize the corresponding responses: + +```kotlin +@Serializable +data class Project(val name: String) + +fun main() { + val responses = listOf( + Response.Ok(Project("kotlinx.serialization")), + Response.Error("Not found") + ) + val string = Json.encodeToString(responses) + println(string) + println(Json.decodeFromString>>(string)) +} +``` + +> You can get the full code [here](../../guide/example/example-json-transform-05.kt). + +This gives you fine-grained control on the representation of the `Response` class in the JSON output: + +```text +[{"name":"kotlinx.serialization"},{"error":"Not found"}] +[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)] +``` + + + +### Maintaining custom JSON attributes + +A good example of custom JSON-specific serializer would be a deserializer +that packs all unknown JSON properties into a dedicated field of `JsonObject` type. + +Let's add `UnknownProject` – a class with the `name` property and arbitrary details flattened into the same object: + + + +```kotlin +data class UnknownProject(val name: String, val details: JsonObject) +``` + +However, the default plugin-generated serializer requires details +to be a separate JSON object and that's not what we want. + +To mitigate that, write an own serializer that uses the fact that it works only with the `Json` format: + +```kotlin +object UnknownProjectSerializer : KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") { + element("name") + element("details") + } + + override fun deserialize(decoder: Decoder): UnknownProject { + // Cast to JSON-specific interface + val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") + // Read the whole content as JSON + val json = jsonInput.decodeJsonElement().jsonObject + // Extract and remove name property + val name = json.getValue("name").jsonPrimitive.content + val details = json.toMutableMap() + details.remove("name") + return UnknownProject(name, JsonObject(details)) + } + + override fun serialize(encoder: Encoder, value: UnknownProject) { + error("Serialization is not supported") + } +} +``` + +Now it can be used to read flattened JSON details as `UnknownProject`: + +```kotlin +fun main() { + println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}""")) +} +``` + +> You can get the full code [here](../../guide/example/example-json-transform-06.kt). + +```text +UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"}) +``` + + diff --git a/guide/example/example-classes-01.kt b/guide/example/example-classes-01.kt index 0330f6556c..3ba2f43a17 100644 --- a/guide/example/example-classes-01.kt +++ b/guide/example/example-classes-01.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses01 import kotlinx.serialization.* @@ -9,15 +9,20 @@ class Project( // name is a property with backing field -- serialized var name: String ) { - var stars: Int = 0 // property with a backing field -- serialized + // stars is property with a backing field -- serialized + var stars: Int = 0 - val path: String // getter only, no backing field -- not serialized + // path is getter only, no backing field -- not serialized + val path: String get() = "kotlin/$name" - var id by ::name // delegated property -- not serialized + // id is a delegated property -- not serialized + var id by ::name } fun main() { val data = Project("kotlinx.serialization").apply { stars = 9000 } + // Only the name and the stars properties are present in the JSON output. println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","stars":9000} } diff --git a/guide/example/example-classes-02.kt b/guide/example/example-classes-02.kt index 969cba9e5b..1ba55574b8 100644 --- a/guide/example/example-classes-02.kt +++ b/guide/example/example-classes-02.kt @@ -1,20 +1,15 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses02 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -class Project private constructor(val owner: String, val name: String) { - constructor(path: String) : this( - owner = path.substringBefore('/'), - name = path.substringAfter('/') - ) - - val path: String - get() = "$owner/$name" -} +// The 'renamedTo' property is nullable and defaults to null, and it's not encoded +class Project(val name: String, val renamedTo: String? = null) fun main() { - println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) + val data = Project("kotlinx.serialization") + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization"} } diff --git a/guide/example/example-classes-03.kt b/guide/example/example-classes-03.kt index cb99131bcb..f3592ab3cb 100644 --- a/guide/example/example-classes-03.kt +++ b/guide/example/example-classes-03.kt @@ -1,19 +1,16 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses03 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -class Project(val name: String) { - init { - require(name.isNotEmpty()) { "name cannot be empty" } - } -} +data class Project(val name: String, val language: String = "Kotlin") fun main() { val data = Json.decodeFromString(""" - {"name":""} + {"name":"kotlinx.serialization","language":null} """) println(data) + // JsonDecodingException } diff --git a/guide/example/example-classes-04.kt b/guide/example/example-classes-04.kt index 5b69111162..92c8b179c9 100644 --- a/guide/example/example-classes-04.kt +++ b/guide/example/example-classes-04.kt @@ -1,15 +1,22 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses04 import kotlinx.serialization.* import kotlinx.serialization.json.* +fun computeLanguage(): String { + println("Computing") + return "Kotlin" +} + @Serializable -data class Project(val name: String, val language: String) +// Initializer is skipped if `language` is in input +data class Project(val name: String, val language: String = computeLanguage()) fun main() { val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} + {"name":"kotlinx.serialization","language":"Java"} """) println(data) + // Project(name=kotlinx.serialization, language=Java) } diff --git a/guide/example/example-classes-05.kt b/guide/example/example-classes-05.kt index 9143b2613c..e95aca4bce 100644 --- a/guide/example/example-classes-05.kt +++ b/guide/example/example-classes-05.kt @@ -1,15 +1,20 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses05 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -data class Project(val name: String, val language: String = "Kotlin") +// The 'owner' property references another serializable class `User` +class Project(val name: String, val owner: User) + +// The referenced class must also be annotated with `@Serializable` +@Serializable +class User(val name: String) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) + val owner = User("kotlin") + val data = Project("kotlinx.serialization", owner) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"}} } diff --git a/guide/example/example-classes-06.kt b/guide/example/example-classes-06.kt index c37b336d11..ca1cd2faf9 100644 --- a/guide/example/example-classes-06.kt +++ b/guide/example/example-classes-06.kt @@ -1,20 +1,19 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses06 import kotlinx.serialization.* import kotlinx.serialization.json.* -fun computeLanguage(): String { - println("Computing") - return "Kotlin" -} +@Serializable +class Project(val name: String, val owner: User, val maintainer: User) @Serializable -data class Project(val name: String, val language: String = computeLanguage()) +class User(val name: String) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) + val owner = User("kotlin") + // 'owner' is referenced twice + val data = Project("kotlinx.serialization", owner, owner) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} } diff --git a/guide/example/example-classes-07.kt b/guide/example/example-classes-07.kt index 19aa153b40..d9022e785a 100644 --- a/guide/example/example-classes-07.kt +++ b/guide/example/example-classes-07.kt @@ -1,15 +1,23 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses07 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -data class Project(val name: String, @Required val language: String = "Kotlin") +// The `Box` class can be used with built-in types like `Int`, or with user-defined types like `Project`. +class Box(val contents: T) +@Serializable +data class Project(val name: String, val language: String) + +@Serializable +class Data( + val a: Box, + val b: Box +) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) + val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) + println(Json.encodeToString(data)) + // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} } diff --git a/guide/example/example-classes-08.kt b/guide/example/example-classes-08.kt index d1cf638d21..745cfd15f1 100644 --- a/guide/example/example-classes-08.kt +++ b/guide/example/example-classes-08.kt @@ -1,15 +1,16 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses08 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -data class Project(val name: String, @Transient val language: String = "Kotlin") +// The language property is abbreviated to lang using @SerialName +class Project(val name: String, @SerialName("lang") val language: String) fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) + val data = Project("kotlinx.serialization", "Kotlin") + // In the JSON output, the abbreviated name lang is used instead of the full property name + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","lang":"Kotlin"} } diff --git a/guide/example/example-classes-09.kt b/guide/example/example-classes-09.kt index 79231f7017..9061e68a8c 100644 --- a/guide/example/example-classes-09.kt +++ b/guide/example/example-classes-09.kt @@ -1,13 +1,21 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses09 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -data class Project(val name: String, val language: String = "Kotlin") +class Project private constructor(val owner: String, val name: String) { + // Creates a Project object using a path string + constructor(path: String) : this( + owner = path.substringBefore('/'), + name = path.substringAfter('/') + ) + val path: String + get() = "$owner/$name" +} fun main() { - val data = Project("kotlinx.serialization") - println(Json.encodeToString(data)) + println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) + // {"owner":"kotlin","name":"kotlinx.serialization"} } diff --git a/guide/example/example-classes-10.kt b/guide/example/example-classes-10.kt index c5a1f739ce..f9be1730ef 100644 --- a/guide/example/example-classes-10.kt +++ b/guide/example/example-classes-10.kt @@ -1,25 +1,21 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses10 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -data class Project( - val name: String, - @EncodeDefault val language: String = "Kotlin" -) - - -@Serializable -data class User( - val name: String, - @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() -) +class Project(val name: String) { + // Validates that the name is not empty + init { + require(name.isNotEmpty()) { "name cannot be empty" } + } +} fun main() { - val userA = User("Alice", listOf(Project("kotlinx.serialization"))) - val userB = User("Bob") - println(Json.encodeToString(userA)) - println(Json.encodeToString(userB)) + val data = Json.decodeFromString(""" + {"name":""} + """) + println(data) + // Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty } diff --git a/guide/example/example-classes-11.kt b/guide/example/example-classes-11.kt index 18a921b961..1b630bf14f 100644 --- a/guide/example/example-classes-11.kt +++ b/guide/example/example-classes-11.kt @@ -1,13 +1,17 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses11 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -class Project(val name: String, val renamedTo: String? = null) +// Sets a default value for the optional `language` property +data class Project(val name: String, val language: String = "Kotlin") fun main() { - val data = Project("kotlinx.serialization") - println(Json.encodeToString(data)) + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization"} + """) + println(data) + // Project(name=kotlinx.serialization, language=Kotlin) } diff --git a/guide/example/example-classes-12.kt b/guide/example/example-classes-12.kt index 232ee47523..dfc85ea127 100644 --- a/guide/example/example-classes-12.kt +++ b/guide/example/example-classes-12.kt @@ -1,15 +1,22 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses12 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.Required +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + @Serializable -data class Project(val name: String, val language: String = "Kotlin") +// Marks the `language` property as required +data class Project(val name: String, @Required val language: String = "Kotlin") fun main() { val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":null} + {"name":"kotlinx.serialization"} """) println(data) + // MissingFieldException } diff --git a/guide/example/example-classes-13.kt b/guide/example/example-classes-13.kt index 4b93c0dff3..4a95a09bca 100644 --- a/guide/example/example-classes-13.kt +++ b/guide/example/example-classes-13.kt @@ -1,17 +1,22 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses13 import kotlinx.serialization.* import kotlinx.serialization.json.* -@Serializable -class Project(val name: String, val owner: User) +// Imports the necessary libraries +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.json.Json @Serializable -class User(val name: String) +// Excludes the `language` property from serialization +data class Project(val name: String, @Transient val language: String = "Kotlin") fun main() { - val owner = User("kotlin") - val data = Project("kotlinx.serialization", owner) - println(Json.encodeToString(data)) + val data = Json.decodeFromString(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + println(data) + // JsonDecodingException } diff --git a/guide/example/example-classes-14.kt b/guide/example/example-classes-14.kt index 3f2d7ce086..c56657c843 100644 --- a/guide/example/example-classes-14.kt +++ b/guide/example/example-classes-14.kt @@ -1,17 +1,14 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses14 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -class Project(val name: String, val owner: User, val maintainer: User) - -@Serializable -class User(val name: String) +data class Project(val name: String, val language: String = "Kotlin") fun main() { - val owner = User("kotlin") - val data = Project("kotlinx.serialization", owner, owner) + val data = Project("kotlinx.serialization") println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization"} } diff --git a/guide/example/example-classes-15.kt b/guide/example/example-classes-15.kt index b259e0d7a3..62308e8a36 100644 --- a/guide/example/example-classes-15.kt +++ b/guide/example/example-classes-15.kt @@ -1,22 +1,32 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses15 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -class Box(val contents: T) - -@Serializable -data class Project(val name: String, val language: String) +data class Project( + val name: String, + // The 'language' property will always be included in the serialized output, even if it has the default value "Kotlin" + @EncodeDefault val language: String = "Kotlin" +) @Serializable -class Data( - val a: Box, - val b: Box +data class User( + val name: String, + // The 'projects' property will never be included in the serialized output, even if it has a value + // Since the default value is an empty list, 'projects' will be omitted unless it contains elements + @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() ) fun main() { - val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) - println(Json.encodeToString(data)) + val userA = User("Alice", listOf(Project("kotlinx.serialization"))) + val userB = User("Bob") + // 'projects' is serialized because it contains a value, and 'language' is always serialized + println(Json.encodeToString(userA)) + // {"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} + + // 'projects' is omitted because it's an empty list and EncodeDefault.Mode is set to NEVER, so it's not serialized + println(Json.encodeToString(userB)) + // {"name":"Bob"} } diff --git a/guide/example/example-json-01.kt b/guide/example/example-json-01.kt index d3fb267148..47f0921c20 100644 --- a/guide/example/example-json-01.kt +++ b/guide/example/example-json-01.kt @@ -1,9 +1,11 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson01 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* +// Creates a custom Json format val format = Json { prettyPrint = true } @Serializable @@ -11,5 +13,7 @@ data class Project(val name: String, val language: String) fun main() { val data = Project("kotlinx.serialization", "Kotlin") + + // Prints the pretty-printed JSON string println(format.encodeToString(data)) } diff --git a/guide/example/example-json-02.kt b/guide/example/example-json-02.kt index 6eda762409..41b1942d78 100644 --- a/guide/example/example-json-02.kt +++ b/guide/example/example-json-02.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson02 import kotlinx.serialization.* @@ -12,6 +12,8 @@ enum class Status { SUPPORTED } data class Project(val name: String, val status: Status, val votes: Int) fun main() { + // Decodes a JSON string with lenient parsing + // Lenient parsing allows unquoted keys, string and enum values, and quoted integers val data = format.decodeFromString(""" { name : kotlinx.serialization, @@ -20,4 +22,5 @@ fun main() { } """) println(data) + // Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000) } diff --git a/guide/example/example-json-03.kt b/guide/example/example-json-03.kt index 77f0ae241c..2772d3d1f9 100644 --- a/guide/example/example-json-03.kt +++ b/guide/example/example-json-03.kt @@ -1,9 +1,10 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson03 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Configures a Json instance to ignore unknown keys val format = Json { ignoreUnknownKeys = true } @Serializable @@ -13,5 +14,7 @@ fun main() { val data = format.decodeFromString(""" {"name":"kotlinx.serialization","language":"Kotlin"} """) + // Decodes the object even though the `Project` class doesn't have the `language` property println(data) + // Project(name=kotlinx.serialization) } diff --git a/guide/example/example-json-04.kt b/guide/example/example-json-04.kt index a8ae148cc5..455f889db1 100644 --- a/guide/example/example-json-04.kt +++ b/guide/example/example-json-04.kt @@ -1,15 +1,20 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson04 import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable +// Maps both "name" and "title" JSON fields to the `name` property data class Project(@JsonNames("title") val name: String) fun main() { - val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") - println(project) - val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") - println(oldProject) + val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") + println(project) + // Project(name=kotlinx.serialization) + + val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") + // Both `name` and `title` Json fields correspond to `name` property + println(oldProject) + // Project(name=kotlinx.coroutines) } diff --git a/guide/example/example-json-05.kt b/guide/example/example-json-05.kt index 809cc9ed61..e51de28e8a 100644 --- a/guide/example/example-json-05.kt +++ b/guide/example/example-json-05.kt @@ -1,9 +1,10 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson05 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Configures a Json instance to encode default values val format = Json { encodeDefaults = true } @Serializable @@ -15,5 +16,8 @@ class Project( fun main() { val data = Project("kotlinx.serialization") + + // Encodes all the property values including the default ones println(format.encodeToString(data)) + // {"name":"kotlinx.serialization","language":"Kotlin","website":null} } diff --git a/guide/example/example-json-06.kt b/guide/example/example-json-06.kt index 776e3ec43e..d4a21388f8 100644 --- a/guide/example/example-json-06.kt +++ b/guide/example/example-json-06.kt @@ -1,9 +1,10 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson06 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Configures a Json instance to omit null values during serialization val format = Json { explicitNulls = false } @Serializable @@ -18,6 +19,13 @@ data class Project( fun main() { val data = Project("kotlinx.serialization", "Kotlin", null, null, null) val json = format.encodeToString(data) + + // The version, website, and description fields are omitted from the output JSON println(json) + // {"name":"kotlinx.serialization","language":"Kotlin"} + + // Missing nullable fields without defaults are treated as null + // Fields with defaults are filled with their default values println(format.decodeFromString(json)) + // Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null) } diff --git a/guide/example/example-json-07.kt b/guide/example/example-json-07.kt index 4d9ad2c0a8..9c584f8ca2 100644 --- a/guide/example/example-json-07.kt +++ b/guide/example/example-json-07.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson07 import kotlinx.serialization.* @@ -13,5 +13,8 @@ fun main() { val data = format.decodeFromString(""" {"name":"kotlinx.serialization","language":null} """) + + // The invalid `null` value for `language` is coerced to its default value println(data) + // Project(name=kotlinx.serialization, language=Kotlin) } diff --git a/guide/example/example-json-08.kt b/guide/example/example-json-08.kt index 501a38eb0d..3e064b625e 100644 --- a/guide/example/example-json-08.kt +++ b/guide/example/example-json-08.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson08 import kotlinx.serialization.* @@ -15,6 +15,9 @@ val json = Json { } fun main() { + + // Decodes `foreground` to its default value and `background` to `null` val brush = json.decodeFromString("""{"foreground":"pink", "background":"purple"}""") - println(brush) + println(brush) + // Brush(foreground=BLACK, background=null) } diff --git a/guide/example/example-json-09.kt b/guide/example/example-json-09.kt index a0ed6329ce..d351f16436 100644 --- a/guide/example/example-json-09.kt +++ b/guide/example/example-json-09.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson09 import kotlinx.serialization.* @@ -14,5 +14,8 @@ fun main() { Project("kotlinx.serialization") to "Serialization", Project("kotlinx.coroutines") to "Coroutines" ) + // Serializes the map with structured keys as a JSON array: + // `[key1, value1, key2, value2,...]`. println(format.encodeToString(map)) + // [{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"] } diff --git a/guide/example/example-json-10.kt b/guide/example/example-json-10.kt index dc528bb64a..408c86b3e8 100644 --- a/guide/example/example-json-10.kt +++ b/guide/example/example-json-10.kt @@ -1,9 +1,10 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson10 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Configures a Json instance to allow special floating-point values val format = Json { allowSpecialFloatingPointValues = true } @Serializable @@ -13,5 +14,8 @@ class Data( fun main() { val data = Data(Double.NaN) + // This example produces the following non-standard JSON output, yet it is a widely used encoding for + // special values in JVM world: println(format.encodeToString(data)) + // {"value":NaN} } diff --git a/guide/example/example-json-11.kt b/guide/example/example-json-11.kt index 31f873151b..bd7d8301d1 100644 --- a/guide/example/example-json-11.kt +++ b/guide/example/example-json-11.kt @@ -1,9 +1,10 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson11 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Configures a Json instance to use a custom class discriminator val format = Json { classDiscriminator = "#class" } @Serializable @@ -11,11 +12,25 @@ sealed class Project { abstract val name: String } +// Specifies a custom serial name for the OwnedProject class @Serializable @SerialName("owned") class OwnedProject(override val name: String, val owner: String) : Project() +// Specifies a custom serial name for the SimpleProject class +@Serializable +@SerialName("simple") +class SimpleProject(override val name: String) : Project() + fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) + val simpleProject: Project = SimpleProject("kotlinx.serialization") + val ownedProject: Project = OwnedProject("kotlinx.coroutines", "kotlin") + + // Serializes SimpleProject with #class: "simple" + println(format.encodeToString(simpleProject)) + // {"#class":"simple","name":"kotlinx.serialization"} + + // Serializes OwnedProject with #class: "owned" + println(format.encodeToString(ownedProject)) + // {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} } diff --git a/guide/example/example-json-12.kt b/guide/example/example-json-12.kt index da76d4c39c..7900660f74 100644 --- a/guide/example/example-json-12.kt +++ b/guide/example/example-json-12.kt @@ -1,16 +1,19 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson12 import kotlinx.serialization.* import kotlinx.serialization.json.* +// The @JsonClassDiscriminator annotation is inheritable, so all subclasses of `Base` will have the same discriminator @Serializable @JsonClassDiscriminator("message_type") sealed class Base -@Serializable // Class discriminator is inherited from Base +// Class discriminator is inherited from Base +@Serializable sealed class ErrorClass: Base() +// Defines a class that combines a message and an optional error @Serializable data class Message(val message: Base, val error: ErrorClass?) @@ -22,10 +25,11 @@ data class BaseMessage(val message: String) : Base() @SerialName("my.app.GenericError") data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() - val format = Json { classDiscriminator = "#class" } fun main() { val data = Message(BaseMessage("not found"), GenericError(404)) + // The discriminator from the `Base` class is used println(format.encodeToString(data)) + // {"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}} } diff --git a/guide/example/example-json-13.kt b/guide/example/example-json-13.kt index 5da592e745..e284154620 100644 --- a/guide/example/example-json-13.kt +++ b/guide/example/example-json-13.kt @@ -1,9 +1,10 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson13 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Configures a Json instance to omit the class discriminator from the output val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } @Serializable @@ -16,5 +17,7 @@ class OwnedProject(override val name: String, val owner: String) : Project() fun main() { val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes without a discriminator println(format.encodeToString(data)) + // {"name":"kotlinx.coroutines","owner":"kotlin"} } diff --git a/guide/example/example-json-14.kt b/guide/example/example-json-14.kt index 67d2e2d32e..fd72d582a4 100644 --- a/guide/example/example-json-14.kt +++ b/guide/example/example-json-14.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson14 import kotlinx.serialization.* diff --git a/guide/example/example-json-15.kt b/guide/example/example-json-15.kt index db504cbff0..e3ceabe8c7 100644 --- a/guide/example/example-json-15.kt +++ b/guide/example/example-json-15.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson15 import kotlinx.serialization.* diff --git a/guide/example/example-json-16.kt b/guide/example/example-json-16.kt index eaa7c90a1c..b7ed045b7b 100644 --- a/guide/example/example-json-16.kt +++ b/guide/example/example-json-16.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.exampleJson16 import kotlinx.serialization.* diff --git a/guide/example/example-json-17.kt b/guide/example/example-json-elements-01.kt similarity index 60% rename from guide/example/example-json-17.kt rename to guide/example/example-json-elements-01.kt index ba7177d6c1..9aa20fde0f 100644 --- a/guide/example/example-json-17.kt +++ b/guide/example/example-json-elements-01.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson17 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements01 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-18.kt b/guide/example/example-json-elements-02.kt similarity index 74% rename from guide/example/example-json-18.kt rename to guide/example/example-json-elements-02.kt index f378615572..e18bc73cf1 100644 --- a/guide/example/example-json-18.kt +++ b/guide/example/example-json-elements-02.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson18 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements02 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-19.kt b/guide/example/example-json-elements-03.kt similarity index 76% rename from guide/example/example-json-19.kt rename to guide/example/example-json-elements-03.kt index 66ce99b7ac..6b70dbc7ad 100644 --- a/guide/example/example-json-19.kt +++ b/guide/example/example-json-elements-03.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson19 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements03 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-20.kt b/guide/example/example-json-elements-04.kt similarity index 71% rename from guide/example/example-json-20.kt rename to guide/example/example-json-elements-04.kt index 8f1c786eee..164f24d887 100644 --- a/guide/example/example-json-20.kt +++ b/guide/example/example-json-elements-04.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson20 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements04 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-21.kt b/guide/example/example-json-elements-05.kt similarity index 77% rename from guide/example/example-json-21.kt rename to guide/example/example-json-elements-05.kt index 2b1d1109cf..c40d92a2df 100644 --- a/guide/example/example-json-21.kt +++ b/guide/example/example-json-elements-05.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson21 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements05 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-22.kt b/guide/example/example-json-elements-06.kt similarity index 82% rename from guide/example/example-json-22.kt rename to guide/example/example-json-elements-06.kt index 952ca30cf3..3fa045164c 100644 --- a/guide/example/example-json-22.kt +++ b/guide/example/example-json-elements-06.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson22 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements06 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-23.kt b/guide/example/example-json-elements-07.kt similarity index 76% rename from guide/example/example-json-23.kt rename to guide/example/example-json-elements-07.kt index 14f70e2330..6cd68623d9 100644 --- a/guide/example/example-json-23.kt +++ b/guide/example/example-json-elements-07.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson23 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements07 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-24.kt b/guide/example/example-json-elements-08.kt similarity index 58% rename from guide/example/example-json-24.kt rename to guide/example/example-json-elements-08.kt index c9138d243e..e3cb6c57b0 100644 --- a/guide/example/example-json-24.kt +++ b/guide/example/example-json-elements-08.kt @@ -1,5 +1,5 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson24 +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements08 import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-json-25.kt b/guide/example/example-json-transform-01.kt similarity index 86% rename from guide/example/example-json-25.kt rename to guide/example/example-json-transform-01.kt index 67c3bf5a83..0c0519d562 100644 --- a/guide/example/example-json-25.kt +++ b/guide/example/example-json-transform-01.kt @@ -1,10 +1,9 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson25 +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.exampleJsonTransform01 import kotlinx.serialization.* -import kotlinx.serialization.json.* - import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* @Serializable data class Project( diff --git a/guide/example/example-json-26.kt b/guide/example/example-json-transform-02.kt similarity index 80% rename from guide/example/example-json-26.kt rename to guide/example/example-json-transform-02.kt index 812e49679a..3d912faf48 100644 --- a/guide/example/example-json-26.kt +++ b/guide/example/example-json-transform-02.kt @@ -1,7 +1,8 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson26 +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.exampleJsonTransform02 import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* import kotlinx.serialization.builtins.* diff --git a/guide/example/example-json-27.kt b/guide/example/example-json-transform-03.kt similarity index 80% rename from guide/example/example-json-27.kt rename to guide/example/example-json-transform-03.kt index e28b50ad6f..d61ac06b05 100644 --- a/guide/example/example-json-27.kt +++ b/guide/example/example-json-transform-03.kt @@ -1,7 +1,8 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson27 +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.exampleJsonTransform03 import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable diff --git a/guide/example/example-json-28.kt b/guide/example/example-json-transform-04.kt similarity index 83% rename from guide/example/example-json-28.kt rename to guide/example/example-json-transform-04.kt index 52ca872c39..3384857bbe 100644 --- a/guide/example/example-json-28.kt +++ b/guide/example/example-json-transform-04.kt @@ -1,7 +1,8 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson28 +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.exampleJsonTransform04 import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* import kotlinx.serialization.builtins.* diff --git a/guide/example/example-json-29.kt b/guide/example/example-json-transform-05.kt similarity index 91% rename from guide/example/example-json-29.kt rename to guide/example/example-json-transform-05.kt index db5f0b9677..8064739f13 100644 --- a/guide/example/example-json-29.kt +++ b/guide/example/example-json-transform-05.kt @@ -1,7 +1,8 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson29 +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.exampleJsonTransform05 import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* import kotlinx.serialization.descriptors.* diff --git a/guide/example/example-json-30.kt b/guide/example/example-json-transform-06.kt similarity index 87% rename from guide/example/example-json-30.kt rename to guide/example/example-json-transform-06.kt index 3aaa045029..6a2b0018bb 100644 --- a/guide/example/example-json-30.kt +++ b/guide/example/example-json-transform-06.kt @@ -1,7 +1,8 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. -package example.exampleJson30 +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.exampleJsonTransform06 import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* import kotlinx.serialization.descriptors.* diff --git a/guide/test/BasicSerializationTest.kt b/guide/test/BasicSerializationTest.kt index 11f9e9f28f..8a9f75bcf5 100644 --- a/guide/test/BasicSerializationTest.kt +++ b/guide/test/BasicSerializationTest.kt @@ -1,35 +1,13 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.test import org.junit.Test import kotlinx.knit.test.* class BasicSerializationTest { - @Test - fun testExampleBasic01() { - captureOutput("ExampleBasic01") { example.exampleBasic01.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.", - "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." - ) - } - - @Test - fun testExampleBasic02() { - captureOutput("ExampleBasic02") { example.exampleBasic02.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" - ) - } - - @Test - fun testExampleBasic03() { - captureOutput("ExampleBasic03") { example.exampleBasic03.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization, language=Kotlin)" - ) - } - @Test fun testExampleClasses01() { - captureOutput("ExampleClasses01") { example.exampleClasses01.main() }.verifyOutputLines( + captureOutput("ExampleClasses01") { example.exampleClasses01.main() }.verifyOutputLinesStart( "{\"name\":\"kotlinx.serialization\",\"stars\":9000}" ) } @@ -37,108 +15,101 @@ class BasicSerializationTest { @Test fun testExampleClasses02() { captureOutput("ExampleClasses02") { example.exampleClasses02.main() }.verifyOutputLines( - "{\"owner\":\"kotlin\",\"name\":\"kotlinx.serialization\"}" + "{\"name\":\"kotlinx.serialization\"}" ) } @Test fun testExampleClasses03() { captureOutput("ExampleClasses03") { example.exampleClasses03.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" java.lang.IllegalArgumentException: name cannot be empty" + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language", + "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value." ) } @Test fun testExampleClasses04() { - captureOutput("ExampleClasses04") { example.exampleClasses04.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $" + captureOutput("ExampleClasses04") { example.exampleClasses04.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, language=Java)" ) } @Test fun testExampleClasses05() { captureOutput("ExampleClasses05") { example.exampleClasses05.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization, language=Kotlin)" + "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"}}" ) } @Test fun testExampleClasses06() { captureOutput("ExampleClasses06") { example.exampleClasses06.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization, language=Kotlin)" + "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"maintainer\":{\"name\":\"kotlin\"}}" ) } @Test fun testExampleClasses07() { - captureOutput("ExampleClasses07") { example.exampleClasses07.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $" + captureOutput("ExampleClasses07") { example.exampleClasses07.main() }.verifyOutputLines( + "{\"a\":{\"contents\":42},\"b\":{\"contents\":{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}}}" ) } @Test fun testExampleClasses08() { - captureOutput("ExampleClasses08") { example.exampleClasses08.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name", - "Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys." + captureOutput("ExampleClasses08") { example.exampleClasses08.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"lang\":\"Kotlin\"}" ) } @Test fun testExampleClasses09() { captureOutput("ExampleClasses09") { example.exampleClasses09.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\"}" + "{\"owner\":\"kotlin\",\"name\":\"kotlinx.serialization\"}" ) } @Test fun testExampleClasses10() { - captureOutput("ExampleClasses10") { example.exampleClasses10.main() }.verifyOutputLines( - "{\"name\":\"Alice\",\"projects\":[{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}]}", - "{\"name\":\"Bob\"}" + captureOutput("ExampleClasses10") { example.exampleClasses10.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" java.lang.IllegalArgumentException: name cannot be empty" ) } @Test fun testExampleClasses11() { captureOutput("ExampleClasses11") { example.exampleClasses11.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\"}" + "Project(name=kotlinx.serialization, language=Kotlin)" ) } @Test fun testExampleClasses12() { captureOutput("ExampleClasses12") { example.exampleClasses12.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language", - "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value." + "Exception in thread \"main\" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $" ) } @Test fun testExampleClasses13() { - captureOutput("ExampleClasses13") { example.exampleClasses13.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"}}" + captureOutput("ExampleClasses13") { example.exampleClasses13.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name", + "Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys." ) } @Test fun testExampleClasses14() { captureOutput("ExampleClasses14") { example.exampleClasses14.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"maintainer\":{\"name\":\"kotlin\"}}" + "{\"name\":\"kotlinx.serialization\"}" ) } @Test fun testExampleClasses15() { captureOutput("ExampleClasses15") { example.exampleClasses15.main() }.verifyOutputLines( - "{\"a\":{\"contents\":42},\"b\":{\"contents\":{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}}}" - ) - } - - @Test - fun testExampleClasses16() { - captureOutput("ExampleClasses16") { example.exampleClasses16.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"lang\":\"Kotlin\"}" + "{\"name\":\"Alice\",\"projects\":[{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}]}", + "{\"name\":\"Bob\"}" ) } } diff --git a/guide/test/JsonTest.kt b/guide/test/JsonTest.kt index 70aea3b60e..3cc0a7a780 100644 --- a/guide/test/JsonTest.kt +++ b/guide/test/JsonTest.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from json.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. package example.test import org.junit.Test @@ -83,6 +83,7 @@ class JsonTest { @Test fun testExampleJson11() { captureOutput("ExampleJson11") { example.exampleJson11.main() }.verifyOutputLines( + "{\"#class\":\"simple\",\"name\":\"kotlinx.serialization\"}", "{\"#class\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @@ -114,121 +115,4 @@ class JsonTest { "{\"project_name\":\"kotlinx.serialization\",\"project_owner\":\"Kotlin\"}" ) } - - @Test - fun testExampleJson16() { - captureOutput("ExampleJson16") { example.exampleJson16.main() }.verifyOutputLines( - "{\"base64Input\":\"Zm9vIHN0cmluZw==\"}", - "foo string" - ) - } - - @Test - fun testExampleJson17() { - captureOutput("ExampleJson17") { example.exampleJson17.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" - ) - } - - @Test - fun testExampleJson18() { - captureOutput("ExampleJson18") { example.exampleJson18.main() }.verifyOutputLines( - "9042" - ) - } - - @Test - fun testExampleJson19() { - captureOutput("ExampleJson19") { example.exampleJson19.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}" - ) - } - - @Test - fun testExampleJson20() { - captureOutput("ExampleJson20") { example.exampleJson20.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization, language=Kotlin)" - ) - } - - @Test - fun testExampleJson21() { - captureOutput("ExampleJson21") { example.exampleJson21.main() }.verifyOutputLines( - "{", - " \"pi_double\": 3.141592653589793,", - " \"pi_string\": \"3.141592653589793238462643383279\"", - "}" - ) - } - - @Test - fun testExampleJson22() { - captureOutput("ExampleJson22") { example.exampleJson22.main() }.verifyOutputLines( - "{", - " \"pi_literal\": 3.141592653589793238462643383279,", - " \"pi_double\": 3.141592653589793,", - " \"pi_string\": \"3.141592653589793238462643383279\"", - "}" - ) - } - - @Test - fun testExampleJson23() { - captureOutput("ExampleJson23") { example.exampleJson23.main() }.verifyOutputLines( - "3.141592653589793238462643383279" - ) - } - - @Test - fun testExampleJson24() { - captureOutput("ExampleJson24") { example.exampleJson24.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive" - ) - } - - @Test - fun testExampleJson25() { - captureOutput("ExampleJson25") { example.exampleJson25.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization, users=[User(name=kotlin)])", - "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])" - ) - } - - @Test - fun testExampleJson26() { - captureOutput("ExampleJson26") { example.exampleJson26.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}" - ) - } - - @Test - fun testExampleJson27() { - captureOutput("ExampleJson27") { example.exampleJson27.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}", - "{\"name\":\"kotlinx.serialization\"}" - ) - } - - @Test - fun testExampleJson28() { - captureOutput("ExampleJson28") { example.exampleJson28.main() }.verifyOutputLines( - "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]", - "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]" - ) - } - - @Test - fun testExampleJson29() { - captureOutput("ExampleJson29") { example.exampleJson29.main() }.verifyOutputLines( - "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]", - "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]" - ) - } - - @Test - fun testExampleJson30() { - captureOutput("ExampleJson30") { example.exampleJson30.main() }.verifyOutputLines( - "UnknownProject(name=example, details={\"type\":\"unknown\",\"maintainer\":\"Unknown\",\"license\":\"Apache 2.0\"})" - ) - } } diff --git a/guide/test/JsonTestElements.kt b/guide/test/JsonTestElements.kt new file mode 100644 index 0000000000..46d2509c2c --- /dev/null +++ b/guide/test/JsonTestElements.kt @@ -0,0 +1,70 @@ +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class JsonTestElements { + @Test + fun testExampleJsonElements01() { + captureOutput("ExampleJsonElements01") { example.exampleJsonElements01.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + ) + } + + @Test + fun testExampleJsonElements02() { + captureOutput("ExampleJsonElements02") { example.exampleJsonElements02.main() }.verifyOutputLines( + "9042" + ) + } + + @Test + fun testExampleJsonElements03() { + captureOutput("ExampleJsonElements03") { example.exampleJsonElements03.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}" + ) + } + + @Test + fun testExampleJsonElements04() { + captureOutput("ExampleJsonElements04") { example.exampleJsonElements04.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, language=Kotlin)" + ) + } + + @Test + fun testExampleJsonElements05() { + captureOutput("ExampleJsonElements05") { example.exampleJsonElements05.main() }.verifyOutputLines( + "{", + " \"pi_double\": 3.141592653589793,", + " \"pi_string\": \"3.141592653589793238462643383279\"", + "}" + ) + } + + @Test + fun testExampleJsonElements06() { + captureOutput("ExampleJsonElements06") { example.exampleJsonElements06.main() }.verifyOutputLines( + "{", + " \"pi_literal\": 3.141592653589793238462643383279,", + " \"pi_double\": 3.141592653589793,", + " \"pi_string\": \"3.141592653589793238462643383279\"", + "}" + ) + } + + @Test + fun testExampleJsonElements07() { + captureOutput("ExampleJsonElements07") { example.exampleJsonElements07.main() }.verifyOutputLines( + "3.141592653589793238462643383279" + ) + } + + @Test + fun testExampleJsonElements08() { + captureOutput("ExampleJsonElements08") { example.exampleJsonElements08.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive" + ) + } +} diff --git a/guide/test/JsonTestTransform.kt b/guide/test/JsonTestTransform.kt new file mode 100644 index 0000000000..5d613681c8 --- /dev/null +++ b/guide/test/JsonTestTransform.kt @@ -0,0 +1,53 @@ +// This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class JsonTestTransform { + @Test + fun testExampleJsonTransform01() { + captureOutput("ExampleJsonTransform01") { example.exampleJsonTransform01.main() }.verifyOutputLines( + "Project(name=kotlinx.serialization, users=[User(name=kotlin)])", + "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])" + ) + } + + @Test + fun testExampleJsonTransform02() { + captureOutput("ExampleJsonTransform02") { example.exampleJsonTransform02.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}" + ) + } + + @Test + fun testExampleJsonTransform03() { + captureOutput("ExampleJsonTransform03") { example.exampleJsonTransform03.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}", + "{\"name\":\"kotlinx.serialization\"}" + ) + } + + @Test + fun testExampleJsonTransform04() { + captureOutput("ExampleJsonTransform04") { example.exampleJsonTransform04.main() }.verifyOutputLines( + "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]", + "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]" + ) + } + + @Test + fun testExampleJsonTransform05() { + captureOutput("ExampleJsonTransform05") { example.exampleJsonTransform05.main() }.verifyOutputLines( + "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]", + "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]" + ) + } + + @Test + fun testExampleJsonTransform06() { + captureOutput("ExampleJsonTransform06") { example.exampleJsonTransform06.main() }.verifyOutputLines( + "UnknownProject(name=example, details={\"type\":\"unknown\",\"maintainer\":\"Unknown\",\"license\":\"Apache 2.0\"})" + ) + } +} From c2bb8261a682e2592933095167db018005cab485 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Tue, 10 Sep 2024 16:14:44 +0200 Subject: [PATCH 05/14] Json updates --- .../serialization-json-configuration.md | 203 ++++++++-------- docs/topics/serialization-json-elements.md | 218 +++++++++++------- docs/topics/serialization-transform-json.md | 71 +++--- 3 files changed, 276 insertions(+), 216 deletions(-) diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md index bfb73888ad..f7648f625e 100644 --- a/docs/topics/serialization-json-configuration.md +++ b/docs/topics/serialization-json-configuration.md @@ -66,7 +66,11 @@ It produces the following result: -## Lenient parsing +## Customizing JSON parsing behavior + +Kotlin’s `Json` parser offers various settings to customize how JSON data is parsed and deserialized. + +### Lenient parsing -## Ignore unknown keys +### Ignore unknown keys The JSON format is often used to process data from third-party services or other dynamic environments where new properties may be added over time. By default, unknown keys encountered during deserialization cause an error. @@ -146,49 +150,13 @@ Project(name=kotlinx.serialization) -## Handle multiple JSON field names with @JsonNames - -When JSON fields are renamed due to schema version changes, -you can use the [`@SerialName`](serialization-customization-options.md#customize-serial-names) annotation to change the name of a JSON field. -However, this approach prevents decoding data with the old name. -To support multiple JSON names for a single Kotlin property, use the [`@JsonNames`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/) annotation: - -```kotlin -@Serializable -// Maps both "name" and "title" JSON fields to the `name` property -data class Project(@JsonNames("title") val name: String) - -fun main() { - val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") - println(project) - // Project(name=kotlinx.serialization) - - val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") - // Both `name` and `title` Json fields correspond to `name` property - println(oldProject) - // Project(name=kotlinx.coroutines) -} -``` - - - - +## Managing default and null values -> The `@JsonNames` annotation is enabled by the [`useAlternativeNames`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html) property in [`JsonBuilder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/). -> This property is set to `true` by default and allows Json to recognize and handle multiple names for a single property. -> If you are not using `@JsonNames` and want to optimize performance, -> especially when skipping many unknown fields with `ignoreUnknownKeys`, you can set this property to `false`. -> -{type="note"} +When working with JSON, managing default and `null` values helps ensure your data stays consistent. +`kotlinx.serialization` provides control over when to include default values, how to handle nulls, +and how to coerce unexpected or missing data into valid values during deserialization. - - -## Encode default values +### Encode default values By default, the JSON serializer does not encode default property values because they are automatically assigned to missing fields during decoding. This behavior is especially useful for nullable properties with null defaults, as it avoids writing unnecessary `null` values. @@ -226,9 +194,9 @@ fun main() { -## Omit explicit nulls +### Omit explicit nulls -By default, all `null` values are encoded into JSON strings. +By default, all `null` values are encoded into JSON strings. To omit `null` values, set the [`explicitNulls`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html) property to `false`: ```kotlin @@ -273,12 +241,12 @@ Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null > You can configure the decoder to handle certain invalid input values by treating them as missing fields using the [`coerceInputValues`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html) property. > For more information, see the [Coerce input values](#coerce-input-values) section. -> +> {type="tip"} -## Coerce input values +### Coerce input values When working with JSON data from third parties, the format can evolve over time, leading to changes in field types. This can lead to exceptions during decoding when the actual values do not match the expected types. @@ -578,15 +546,64 @@ fun main() { -## Decode enums in a case-insensitive manner +## Naming conventions and formats + +In some scenarios, JSON data may not perfectly align with Kotlin’s naming conventions or expected formats. +To address these challenges, `kotlinx.serialization` provides several tools to manage naming discrepancies, +handle multiple JSON field names, and ensure consistent naming strategies across your serialized data. + +### Handle multiple JSON field names with @JsonNames + +When JSON fields are renamed due to schema version changes, +you can [use the `@SerialName` annotation to change the name of a JSON field](serialization-customization-options.md#customize-serial-names). +However, this approach prevents decoding data with the old name. +To support multiple JSON names for a single Kotlin property, use the [`@JsonNames`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/) annotation: + +```kotlin +@Serializable +// Maps both "name" and "title" JSON fields to the `name` property +data class Project(@JsonNames("title") val name: String) + +fun main() { + val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") + println(project) + // Project(name=kotlinx.serialization) + + val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") + // Both `name` and `title` Json fields correspond to `name` property + println(oldProject) + // Project(name=kotlinx.coroutines) +} +``` + + + + + +> The `@JsonNames` annotation is enabled by the [`useAlternativeNames`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html) property in [`JsonBuilder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/). +> This property is set to `true` by default and allows Json to recognize and handle multiple names for a single property. +> If you are not using `@JsonNames` and want to optimize performance, +> especially when skipping many unknown fields with `ignoreUnknownKeys`, you can set this property to `false`. +> +{type="note"} + + + +### Decode enums in a case-insensitive manner -[Kotlin's naming policy recommends](https://kotlinlang.org/docs/coding-conventions.html#property-names) naming enum values +[Kotlin's naming policy recommends](coding-conventions.md#property-names) naming enum values using either uppercase underscore-separated names or upper camel case names. -[Json] uses exact Kotlin enum values names for decoding by default. -However, sometimes third-party JSONs have such values named in lowercase or some mixed case. -In this case, it is possible to decode enum values in a case-insensitive manner using [JsonBuilder.decodeEnumsCaseInsensitive] property: +By default, `Json` uses these exact Kotlin enum values names for decoding. +However, third-party JSONs might have enum values in lowercase or mixed case. +To handle such cases, you can configure the `Json` instance to decode enum values in a case-insensitive way using the [`JsonBuilder.decodeEnumsCaseInsensitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html) property: ```kotlin +// Configures a Json instance to decode enum values in a case-insensitive way val format = Json { decodeEnumsCaseInsensitive = true } enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } @@ -595,13 +612,18 @@ enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } data class CasesList(val cases: List) fun main() { - println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) + // Decodes enum values regardless of their case, affecting both serial names and alternative names + println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) + // CasesList(cases=[VALUE_A, VALUE_B]) } ``` - +> This property affects both serial names and alternative names specified with the `@JsonNames` annotation, +> ensuring that all values are successfully decoded. This property does not affect encoding. +> +{type="note"} -It affects serial names as well as alternative names specified with [JsonNames] annotation, so both values are successfully decoded: + -This property does not affect encoding in any way. - -## Global naming strategy +### Apply a global naming strategy -If properties' names in Json input are different from Kotlin ones, it is recommended to specify the name -for each property explicitly using [`@SerialName` annotation](basic-serialization.md#serial-field-names). -However, there are certain situations where transformation should be applied to every serial name — such as migration -from other frameworks or legacy codebase. For that cases, it is possible to specify a [namingStrategy][JsonBuilder.namingStrategy] -for a [Json] instance. `kotlinx.serialization` provides one strategy implementation out of the box, the [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html): +When the property names in JSON input differ from those in Kotlin, you can specify the name for each property explicitly using the [`@SerialName`](serialization-customization-options.md#customize-serial-names) annotation. +However, in cases like migrating from other frameworks or a legacy codebase, you might need to apply a transformation to every serial name. +For these scenarios, you can specify a global naming strategy using the [JsonBuilder.namingStrategy](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/naming-strategy.html) property in a `Json` instance. +`kotlinx.serialization` provides a built-in strategy, [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html): ```kotlin @Serializable data class Project(val projectName: String, val projectOwner: String) +// Configures a Json instance to apply SnakeCase naming strategy val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase } fun main() { val project = format.decodeFromString("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""") + // Serializes and deserializes as if all serial names are transformed from camel case to snake case println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) + // {"project_name":"kotlinx.serialization","project_owner":"Kotlin"} } ``` -As you can see, both serialization and deserialization work as if all serial names are transformed from camel case to snake case: - -There are some caveats one should remember while dealing with a [JsonNamingStrategy]: - -* Due to the nature of the `kotlinx.serialization` framework, naming strategy transformation is applied to all properties regardless - of whether their serial name was taken from the property name or provided by [SerialName] annotation. - Effectively, it means one cannot avoid transformation by explicitly specifying the serial name. To be able to deserialize - non-transformed names, [JsonNames] annotation can be used instead. +When using a global naming strategy like [JsonNamingStrategy](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/), keep the following in mind: -* Collision of the transformed name with any other (transformed) properties serial names or any alternative names - specified with [JsonNames] will lead to a deserialization exception. +* The naming strategy transformation applies to all properties, whether the serial name comes from the property name or is specified by the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation. +This means you cannot avoid the transformation by explicitly specifying a serial name. +To ensure certain names are kept during serialization, consider using the `@JsonNames` annotation instead. +* If a transformed name conflicts with other transformed property names or with alternative names specified by the `@JsonNames` annotation, +it will result in a deserialization exception. +* Global naming strategies are implicit. +By just looking at a class definition, it’s hard to tell what the serialized names will be. +This can make tasks like **Find Usages**, **Rename** in IDEs, or full-text search by tools like `grep` challenging, potentially introducing bugs or increasing maintenance efforts. -* Global naming strategies are very implicit: by looking only at the definition of the class, - it is impossible to determine which names it will have in the serialized form. - As a consequence, naming strategies are not friendly to actions like Find Usages/Rename in IDE, full-text search by grep, etc. - For them, the original name and the transformed are two different things; - changing one without the other may introduce bugs in many unexpected ways and lead to greater maintenance efforts for code with global naming strategies. - -Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application. +Given these factors, it’s important to weigh the pros and cons before implementing global naming strategies in your application. - - - -[RFC-4627]: https://www.ietf.org/rfc/rfc4627.txt -[BigDecimal]: https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html - - -[Double]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/ -[Double.NaN]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html -[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/ -[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/ - - - - -[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html - - - - - -[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html -[JsonBuilder.decodeEnumsCaseInsensitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html -[JsonNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html -[JsonBuilder.namingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/naming-strategy.html -[JsonNamingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/index.html - - diff --git a/docs/topics/serialization-json-elements.md b/docs/topics/serialization-json-elements.md index 35006a9ac4..d3047123d9 100644 --- a/docs/topics/serialization-json-elements.md +++ b/docs/topics/serialization-json-elements.md @@ -1,18 +1,22 @@ [//]: # (title: Managing JSON elements) -Aside from direct conversions between strings and JSON objects, Kotlin serialization offers APIs that allow -other ways of working with JSON in the code. For example, you might need to tweak the data before it can parse -or otherwise work with such an unstructured data that it does not readily fit into the typesafe world of Kotlin -serialization. +Kotlin serialization provides APIs that allow more than just direct conversions between strings and JSON objects. +For example, you might want to modify data before it can be parsed or work with unstructured data that doesn't fit neatly into the type-safe world of Kotlin serialization. -The main concept in this part of the library is [JsonElement]. Read on to learn what you can do with it. +This section focuses on [`JsonElement`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/), a core concept used to represent and manipulate unstructured JSON data. -### Parsing to Json element +Before diving into specific features, ensure that the necessary libraries are imported: -A string can be _parsed_ into an instance of [JsonElement] with the [Json.parseToJsonElement] function. -It is called neither decoding nor deserialization because none of that happens in the process. -It just parses a JSON and forms an object representing it: +```kotlin +import kotlinx.serialization.* +import kotlinx.serialization.json.* +``` + +## Parse to Json element + +You can parse a string into an instance of `JsonElement` with the [`Json.parseToJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html) function. +This process is not considered decoding or deserialization, as it simply parses the JSON and creates an object that represents it: -A `JsonElement` prints itself as a valid JSON: - -### Types of Json elements +## Types of Json elements + +A `JsonElement` class has three direct subtypes, which align with JSON structure: -A [JsonElement] class has three direct subtypes, closely following JSON grammar: +* [`JsonPrimitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/) represents primitive JSON elements, such as strings, numbers, booleans, and nulls. + Each `JsonPrimitive` holds a string representation of its value, accessible through its [`JsonPrimitive.content`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/content.html) property. + You can also create a `JsonPrimitive` by passing primitive Kotlin types to the [`JsonPrimitive()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html) constructor. +* [`JsonArray`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-array/) represents a JSON `[...]` array. It is a Kotlin [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/) of `JsonElement` items. +* [`JsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/) represents a JSON `{...}` object. It is a Kotlin [`Map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/#kotlin.collections.Map) consisting of `String` keys and `JsonElement` values. -* [JsonPrimitive] represents primitive JSON elements, such as string, number, boolean, and null. - Each primitive has a simple string [content][JsonPrimitive.content]. There is also a - [JsonPrimitive()] constructor function overloaded to accept various primitive Kotlin types and - to convert them to `JsonPrimitive`. +The `JsonElement` class provides convenience functions to check the type of an element. +If the element is not of the expected type, an IllegalArgumentException is thrown: -* [JsonArray] represents a JSON `[...]` array. It is a Kotlin [List] of `JsonElement` items. +* [`jsonPrimitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-primitive.html) checks if the element is a `JsonPrimitive`. +* [`jsonArray`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-array.html) checks if the element is a `JsonArray`. +* [`jsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-object.html) checks if the element is a `JsonObject`. -* [JsonObject] represents a JSON `{...}` object. It is a Kotlin [Map] from `String` keys to `JsonElement` values. +The `JsonPrimitive` class, +class also includes functions to convert its value into various Kotlin primitive types. +For example, `int` converts the value to an `Int`, throwing an exception if the value is not a valid integer, while `intOrNull` safely converts the value to an `Int`, returning `null` if the conversion fails. +Similar functions are available for other types, such as `long`, `longOrNull`, `double`, and `doubleOrNull`. -The `JsonElement` class has extensions that cast it to its corresponding subtypes: -[jsonPrimitive][_jsonPrimitive], [jsonArray][_jsonArray], [jsonObject][_jsonObject]. The `JsonPrimitive` class, -in turn, provides converters to Kotlin primitive types: [int], [intOrNull], [long], [longOrNull], -and similar ones for other types. This is how you can use them for processing JSON whose structure you know: +Here is an example of how you can use these functions when processing JSON data: ```kotlin fun main() { @@ -66,41 +76,48 @@ fun main() { "forks": [{"votes": 42}, {"votes": 9000}, {}] } """) + // Sums `votes` in all objects in the `forks` array, ignoring the objects without `votes` val sum = element + // Accesses the "forks" key from the root JsonObject .jsonObject["forks"]!! + + // Checks that "forks" is a JsonArray and sums the "votes" from each JsonObject .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 } println(sum) + // 9042 } ``` -> You can get the full code [here](../../guide/example/example-json-elements-02.kt). - -The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`: + + -Note that the execution will fail if the structure of the data is otherwise different. +## Construct JSON elements with builder functions -### Json element builders +You can construct instances of specific `JsonElement` subtypes using the respective builder functions +[`buildJsonArray()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-array.html) and [`buildJsonObject()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html). +These provide a DSL for defining the JSON structure, similar to Kotlin’s standard library collection builders but with JSON-specific overloads and inner builder functions. -You can construct instances of specific [JsonElement] subtypes using the respective builder functions -[buildJsonArray] and [buildJsonObject]. They provide a DSL to define the resulting JSON structure. It -is similar to Kotlin standard library collection builders, but with a JSON-specific convenience -of more type-specific overloads and inner builder functions. The following example shows -all the key features: +Here is an example demonstrating the key features: ```kotlin fun main() { val element = buildJsonObject { + // Adds a simple key-value pair to the JsonObject put("name", "kotlinx.serialization") + // Adds a nested JsonObject under the "owner" key putJsonObject("owner") { put("name", "kotlin") } + // Adds a JsonArray with multiple JsonObjects putJsonArray("forks") { + // Adds a JsonObject to the JsonArray addJsonObject { put("votes", 42) } @@ -109,24 +126,26 @@ fun main() { } } } + // Prints the resulting JSON string println(element) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} } ``` -> You can get the full code [here](../../guide/example/example-json-elements-03.kt). - -As a result, you get a proper JSON string: + + -### Decoding Json elements +## Decode Json elements -An instance of the [JsonElement] class can be decoded into a serializable object using -the [Json.decodeFromJsonElement] function: +You can decode an instance of the `JsonElement` class into a serializable object using +the [`Json.decodeFromJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) function: ```kotlin @Serializable @@ -137,36 +156,38 @@ fun main() { put("name", "kotlinx.serialization") put("language", "Kotlin") } + + // Decodes the JsonElement into a Project object val data = Json.decodeFromJsonElement(element) println(data) + // Project(name=kotlinx.serialization, language=Kotlin) } ``` -> You can get the full code [here](../../guide/example/example-json-elements-04.kt). - -The result is exactly what you would expect: + + -### Encoding literal Json content (experimental) - -> This functionality is experimental and requires opting-in to [the experimental Kotlinx Serialization API](compatibility.md#experimental-api). +## Encode literal Json content (experimental) -In some cases it might be necessary to encode an arbitrary unquoted value. -This can be achieved with [JsonUnquotedLiteral]. +> The [`JsonUnquotedLiteral`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html) functionality is [Experimental](components-stability.md#stability-levels-explained). To opt in, use the `@ExperimentalSerializationApi` annotation or the compiler option -opt-in=kotlinx.serialization.ExperimentalSerializationApi. +> +{type="warning"} -#### Serializing large decimal numbers +You can encode an arbitrary unquoted value with [`JsonUnquotedLiteral`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html). -The JSON specification does not restrict the size or precision of numbers, however it is not possible to serialize -numbers of arbitrary size or precision using [JsonPrimitive()]. +While the JSON specification does not restrict the size or precision of numbers, serializing +numbers of arbitrary size or precision using [`JsonPrimitive()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html) is limited. -If [Double] is used, then the numbers are limited in precision, meaning that large numbers are truncated. -When using Kotlin/JVM [BigDecimal] can be used instead, but [JsonPrimitive()] will encode the value as a string, not a -number. +If you use [`Double`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/), the value might be truncated, which can lead to a loss of accuracy for large numbers. +Kotlin/JVM [`BigDecimal`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/) can represent +large numbers without loss of precision, but using `JsonPrimitive()` encodes the value as a string rather than as a number: ```kotlin import java.math.BigDecimal @@ -176,7 +197,9 @@ val format = Json { prettyPrint = true } fun main() { val pi = BigDecimal("3.141592653589793238462643383279") + // Converts the BigDecimal to a Double, causing potential truncation val piJsonDouble = JsonPrimitive(pi.toDouble()) + // Converts the BigDecimal to a String, preserving the precision but treating it as a string in JSON val piJsonString = JsonPrimitive(pi.toString()) val piObject = buildJsonObject { @@ -185,24 +208,28 @@ fun main() { } println(format.encodeToString(piObject)) + // "pi_double": 3.141592653589793, + // "pi_string": "3.141592653589793238462643383279" } ``` -> You can get the full code [here](../../guide/example/example-json-elements-05.kt). + -Even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. -The [Double] value is truncated to 15 decimal places, and the String is wrapped in quotes - which is not a JSON number. +In the example above, even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. +The `Double` value is truncated to 15 decimal places, and the `String` is wrapped in quotes, making it a string instead of a JSON number. + -To avoid precision loss, the string value of `pi` can be encoded using [JsonUnquotedLiteral]. +To avoid precision loss, you can encode an arbitrary unquoted value, such as the string value of `pi` in this example, using [`JsonUnquotedLiteral`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html): ```kotlin import java.math.BigDecimal @@ -212,26 +239,31 @@ val format = Json { prettyPrint = true } fun main() { val pi = BigDecimal("3.141592653589793238462643383279") - // use JsonUnquotedLiteral to encode raw JSON content + // Encodes the raw JSON content using JsonUnquotedLiteral + @OptIn(ExperimentalSerializationApi::class) val piJsonLiteral = JsonUnquotedLiteral(pi.toString()) + // Converts to Double and String val piJsonDouble = JsonPrimitive(pi.toDouble()) val piJsonString = JsonPrimitive(pi.toString()) - + val piObject = buildJsonObject { put("pi_literal", piJsonLiteral) put("pi_double", piJsonDouble) put("pi_string", piJsonString) } + // `pi_literal` now accurately matches the value defined. println(format.encodeToString(piObject)) + // "pi_literal": 3.141592653589793238462643383279, + // "pi_double": 3.141592653589793, + // "pi_string": "3.141592653589793238462643383279" } ``` -> You can get the full code [here](../../guide/example/example-json-elements-06.kt). - -`pi_literal` now accurately matches the value defined. + + -To decode `pi` back to a [BigDecimal], the string content of the [JsonPrimitive] can be used. - -(This demonstration uses a [JsonPrimitive] for simplicity. For a more re-usable method of handling serialization, see -[Json Transformations](#json-transformations) below.) - +To decode `pi` back to a `BigDecimal`, you can extract the string content of the `JsonPrimitive`: ```kotlin import java.math.BigDecimal @@ -257,43 +286,74 @@ fun main() { "pi_literal": 3.141592653589793238462643383279 } """.trimIndent() - + + // Decodes the JSON string into a JsonObject val piObject: JsonObject = Json.decodeFromString(piObjectJson) - + + // Extracts the string content from the JsonPrimitive val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content - + + // Converts the string to a BigDecimal val pi = BigDecimal(piJsonLiteral) - + // Prints the decoded value of pi, preserving all 30 decimal places println(pi) + // 3.141592653589793238462643383279 } ``` -> You can get the full code [here](../../guide/example/example-json-elements-07.kt). +> This example uses a `JsonPrimitive` for simplicity. For a more reusable method of handling serialization, see +> [Json Transformations](serialization-transform-json.md).) +> +{type="note"} -The exact value of `pi` is decoded, with all 30 decimal places of precision that were in the source JSON. + + -#### Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden - -To avoid creating an inconsistent state, encoding a String equal to `"null"` is forbidden. -Use [JsonNull] or [JsonPrimitive] instead. +Finally, to avoid creating an inconsistent state, encoding a string equal to `"null"` with `JsonUnquotedLiteral` results in an exception. ```kotlin +@OptIn(ExperimentalSerializationApi::class) fun main() { - // caution: creating null with JsonUnquotedLiteral will cause an exception! + // Caution: creating null with JsonUnquotedLiteral causes an exception! JsonUnquotedLiteral("null") + // Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException } ``` -> You can get the full code [here](../../guide/example/example-json-elements-08.kt). + + + +You can use [`JsonNull`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-null/) or `JsonPrimitive` to represent a proper JSON `null` value instead: + +```kotlin +fun main() { + val possiblyNull = JsonNull + + println(possiblyNull) + // null +} +``` + + + + + + diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md index 67ace93fc9..45010fc3f0 100644 --- a/docs/topics/serialization-transform-json.md +++ b/docs/topics/serialization-transform-json.md @@ -1,28 +1,37 @@ -[//]: # (title: Transform JSON during serialization and deserialization) +[//]: # (title: Transform JSON output) -To affect the shape and contents of JSON output after serialization, or adapt input to deserialization, -it is possible to write a [custom serializer](serializers.md). However, it may be inconvenient to -carefully follow [Encoder] and [Decoder] calling conventions, especially for relatively small and easy tasks. -For that purpose, Kotlin serialization provides an API that can reduce the burden of implementing a custom -serializer to a problem of manipulating a Json elements tree. +To modify the structure and content of JSON after serialization, or adapt input for deserialization, you can create a [custom serializer](create-custom-serializers.md). +While the [`Encoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/) +and [`Decoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/) offer precise control, +Kotlin serialization also provides an API that makes it easy to manipulate a JSON elements tree. +This can be ideal for smaller tasks or quick transformations. -We recommend that you get familiar with the [Serializers](serializers.md) chapter: among other things, it -explains how custom serializers are bound to classes. +> For an extensive guide about how to create custom serializers and how custom serializers are bound to classes, see [Create custom serializers](create-custom-serializers.md). +> +{type="note"} -Transformation capabilities are provided by the abstract [JsonTransformingSerializer] class which implements [KSerializer]. -Instead of direct interaction with `Encoder` or `Decoder`, this class asks you to supply transformations for JSON tree -represented by the [JsonElement] class using the`transformSerialize` and -`transformDeserialize` methods. Let's take a look at the examples. +The transformation features are available through the abstract [`JsonTransformingSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/) class, which implements [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/). +Instead of interacting directly with `Encoder` or `Decoder`, this class allows you to define transformations using the +`transformSerialize()` and `transformDeserialize()` functions for the JSON tree +represented by the [`JsonElement`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/) class. -### Array wrapping +Before exploring the specific features in the following sections, ensure that the following libraries are imported: + +```kotlin +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +``` + +## Array wrapping The first example is an implementation of JSON array wrapping for lists. Consider a REST API that returns a JSON array of `User` objects, or a single object (not wrapped into an array) if there is only one element in the result. -In the data model, use the [`@Serializable`][Serializable] annotation to specify a custom serializer for a +In the data model, use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation to specify a custom serializer for a `users: List` property. ```kotlin +// Uses UserListSerializer to handle the serialization of the users property @Serializable data class Project( val name: String, @@ -39,49 +49,52 @@ data class Project( val users: List ) +// Defines the User data class @Serializable data class User(val name: String) -``` - -Since this example covers only the deserialization case, you can implement `UserListSerializer` and override only the -`transformDeserialize` function. The `JsonTransformingSerializer` constructor takes an original serializer -as parameter (this approach is shown in the section [Constructing collection serializers](serializers.md#constructing-collection-serializers)): -```kotlin +// Implements a custom serializer that wraps single objects into arrays during deserialization object UserListSerializer : JsonTransformingSerializer>(ListSerializer(User.serializer())) { - // If response is not an array, then it is a single object that should be wrapped into the array + // If response is not an array, then it is a single object that should be wrapped in an array override fun transformDeserialize(element: JsonElement): JsonElement = if (element !is JsonArray) JsonArray(listOf(element)) else element } -``` -Now you can test the code with a JSON array or a single JSON object as inputs. - -```kotlin fun main() { + // Deserializes a single JSON object response wrapped as an array println(Json.decodeFromString(""" {"name":"kotlinx.serialization","users":{"name":"kotlin"}} """)) + // Project(name=kotlinx.serialization, users=[User(name=kotlin)]) + + // Deserializes a JSON array of objects println(Json.decodeFromString(""" {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]} """)) + // Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) } ``` -> You can get the full code [here](../../guide/example/example-json-transform-01.kt). +Since this example covers only the deserialization case, you can implement `UserListSerializer` and override only the +`transformDeserialize()` function. The `JsonTransformingSerializer` constructor takes an original serializer +as parameter (this approach is shown in the section [Constructing collection serializers](serializers.md#constructing-collection-serializers)): + + The output shows that both cases are correctly deserialized into a Kotlin [List]. + -### Array unwrapping +## Array unwrapping -You can also implement the `transformSerialize` function to unwrap a single-element list into a single JSON object +You can also implement the `transformSerialize()` function to unwrap a single-element list into a single JSON object during serialization: -### Manipulating default values +## Manipulate default values Another kind of useful transformation is omitting specific values from the output JSON, for example, if it is used as default when missing or for other reasons. From cfc9fc88eff88f4fd0a4ec796e44718ee4c9ae10 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Wed, 18 Sep 2024 16:50:36 +0200 Subject: [PATCH 06/14] continuing restructuring --- docs/serialization.tree | 3 +- .../serialization-customization-options.md | 8 +- docs/topics/serialization-get-started.md | 29 +- docs/topics/serialization-polymorphism.md | 1119 ++++++++++++++++- .../serialization-serialize-builtin-types.md | 50 + docs/topics/serialization-transform-json.md | 219 ++-- docs/topics/serialization.md | 25 +- 7 files changed, 1296 insertions(+), 157 deletions(-) create mode 100644 docs/topics/serialization-serialize-builtin-types.md diff --git a/docs/serialization.tree b/docs/serialization.tree index bb86ff8cf9..286e9e7c04 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -10,7 +10,8 @@ - + + diff --git a/docs/topics/serialization-customization-options.md b/docs/topics/serialization-customization-options.md index 0ad5da3d89..a4ed632f51 100644 --- a/docs/topics/serialization-customization-options.md +++ b/docs/topics/serialization-customization-options.md @@ -1,5 +1,5 @@ -[//]: # (title: Serializable classes) +[//]: # (title: Serialize classes) The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. However, you can customize this behavior to fit your specific needs. @@ -375,9 +375,9 @@ Exception in thread "main" java.lang.IllegalArgumentException: name cannot be em ### Set default values for optional properties In Kotlin, an object can only be deserialized when all its properties are present in the input. -If a property is missing, deserialization will fail. +If a property is missing, deserialization fails. -This issue can be resolved by adding a default value to the property, which automatically makes it optional for +To resolve this issue, you can add a default value to the property, which automatically makes it optional for serialization: ```kotlin @@ -482,7 +482,7 @@ Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys. > {type="tip"} -### Manage serialization of default properties with @EncodedDefault +### Manage the serialization of default properties with @EncodedDefault In JSON, default values are not encoded by default. This behavior improves efficiency by reducing visual clutter and minimizing the amount of serialized data. diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md index 4a585a82ea..b00190ba8f 100644 --- a/docs/topics/serialization-get-started.md +++ b/docs/topics/serialization-get-started.md @@ -155,9 +155,8 @@ Let's look at an example: 1. Import the necessary serialization libraries: ```kotlin - import kotlinx.serialization.Serializable - import kotlinx.serialization.json.Json - import kotlinx.serialization.encodeToString + import kotlinx.serialization.* + import kotlinx.serialization.json.* ``` 2. Make a class serializable by annotating it with `@Serializable`. @@ -167,8 +166,8 @@ Let's look at an example: data class Data(val a: Int, val b: String) ``` - > The `@Serialization` annotation enables default serialization of all properties in the primary constructor, - > but you can adjust this behavior using various techniques. These include serialization of backing fields, defining + > The `@Serialization` annotation enables default serialization of all properties in the primary constructor. + > You can adjust this behavior using various techniques. These include serialization of backing fields, defining > custom constructors, specifying optional properties, marking properties as required and more. > These techniques allow for precise control over which properties are serialized and how the serialization process is managed. > For more information, see [Serialization customization options](serialization-customization-options.md). @@ -239,9 +238,8 @@ To deserialize an object from JSON in Kotlin: 1. Import the necessary serialization libraries: ```kotlin - import kotlinx.serialization.Serializable - import kotlinx.serialization.json.Json - import kotlinx.serialization.decodeFromString + import kotlinx.serialization.* + import kotlinx.serialization.json.* ``` 2. Make a class serializable by annotating it with `@Serializable`: @@ -254,19 +252,24 @@ To deserialize an object from JSON in Kotlin: 3. Use the `Json.decodeFromString()` function to deserialize an object from JSON: ```kotlin - import kotlinx.serialization.Serializable - import kotlinx.serialization.json.Json - import kotlinx.serialization.decodeFromString + // Imports the necessary libraries for serialization and JSON handling + import kotlinx.serialization.* + import kotlinx.serialization.json.* + // Marks the Data class as serializable @Serializable data class Data(val a: Int, val b: String) fun main() { - val obj = Json.decodeFromString("""{"a":42, "b": "str"}""") + // Deserializes a JSON string into an instance of the Data class + val obj = Json.decodeFromString("""{"a":42, "b": "str"}""") + println(obj) + // Data(a=42, b="str") } ``` ## What's next? +* Discover various techniques for adjusting serialization behavior in [Serialization customization options](serialization-customization-options.md). * Learn how to create custom serializers in [Create custom serializers](create-custom-serializers.md). -* Discover various techniques for adjusting serialization behavior in [Serialization customization options](serialization-customization-options.md). \ No newline at end of file +* \ No newline at end of file diff --git a/docs/topics/serialization-polymorphism.md b/docs/topics/serialization-polymorphism.md index 9ad43f549b..dd2d6e64d2 100644 --- a/docs/topics/serialization-polymorphism.md +++ b/docs/topics/serialization-polymorphism.md @@ -1,24 +1,1119 @@ [//]: # (title: Serialize polymorphic classes) + + Polymorphism allows objects of different types to be treated as objects of a common supertype. In Kotlin, polymorphism enables you to work with various subclasses through a shared interface or superclass. This is useful for designing flexible and reusable code, especially when the exact type of objects isn't known at compile time. In the context of serialization, polymorphism helps manage data structures where the runtime type of data can vary. -## Closed polymorphism +## Closed polymorphism in Kotlin serialization + +Closed polymorphism ensures that all possible subclasses are known at compile-time, +making it a reliable and predictable approach for serializing and deserializing polymorphic data structures. +The best way to serialize closed polymorphic classes is to use [`sealed classes`](#serialize-closed-polymorphic-classes), which restrict subclassing to the same file. + +Kotlin Serialization is fully static by default, where the structure of encoded objects is based on their compile-time types. +This means that only the properties defined in the static type are serialized, even if the object is initialized with a subclass at runtime. + +Let's look at an example where an `open class Project` has a `name` property, and its subclass `class OwnedProject` adds an `owner` property. +Although the `data` variable is initialized with an instance of `OwnedProject` at runtime, its compile-time type is `Project`. +As a result, only the properties of the `Project` class are serialized: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +// Defines an open class Project with a name property +@Serializable +open class Project(val name: String) + +// Defines a derived class OwnedProject with an additional owner property +class OwnedProject(name: String, val owner: String) : Project(name) + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes data based on the static type Project, ignoring the OwnedProject properties + println(Json.encodeToString(data)) + // {"name":"kotlinx.coroutines"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +In this example, the `owner` property is not serialized because `data` is statically typed as `Project`. + +Changing the compile-time type of `data` to `OwnedProject` throws an exception: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + + +@Serializable +open class Project(val name: String) + +class OwnedProject(name: String, val owner: String) : Project(name) + +fun main() { +//sampleStart + val data = OwnedProject("kotlinx.coroutines", "kotlin") + // Throws an exception, because the `OwnedProject` class is not serializable + println(Json.encodeToString(data)) + // Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found. +} +//sampleEnd +``` +{kotlin-runnable="true" validate="false"} + +This exception occurs because the `OwnedProject` class is not serializable and cannot be annotated with `@Serializable` +as its primary constructor parameters (`name` and `owner`) are not properties. +To resolve this, you can [define constructor properties for serialization](serialization-customization-options.md#define-constructor-properties-for-serialization). + + + + + + + +Alternatively, you might consider defining `Project` as an [`abstract class`](classes.md#abstract-classes) with abstract properties. +That way, instead of `Project` providing actual values for its properties, its subclasses are responsible for defining them. +Unfortunately, this still throws an exception: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +abstract class Project { + abstract val name: String +} + +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + println(Json.encodeToString(data)) + // Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'. +} +//sampleEnd +``` +{kotlin-runnable="true"} + +This exception indicates that defining `Project` as an abstract class doesn't resolve the serialization issue. +To fix this, you need to [use sealed classes](#serialize-closed-polymorphic-classes). + + + + + + + +### Serialize closed polymorphic classes + +The simplest way to handle polymorphic serialization is to use a `sealed class` as the base. +_All_ subclasses of a sealed class must be explicitly marked as `@Serializable`. +This approach ensures that polymorphism is represented in JSON with a type discriminator: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +// Serializes data of compile-time type Project +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // A `type` key is added to the resulting JSON object as a discriminator. + println(Json.encodeToString(data)) + // {"type":"OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +The `type` key in the JSON output identifies the specific subclass being serialized, +which is useful for distinguishing subclasses in polymorphic hierarchies. + +It's important to ensure that the [static type](#closed-polymorphism-in-kotlin-serialization) of the serialized object refers to the base class, +which allows the `type` discriminator to be included in the JSON output. +If the static type refers to a subclass, the discriminator is omitted: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + // The static type is OwnedProject + val data = OwnedProject("kotlinx.coroutines", "kotlin") + + // The `type` discriminator is not included because the static type is OwnedProject. + println(Json.encodeToString(data)) + // {"name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +To ensure consistency in polymorphic serialization, the type used during serialization must match the expected type during deserialization. +You can explicitly specify the base type when serializing objects to ensure the `type` discriminator is included in the output: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + // Sets the static type as OwnedProject + val data = OwnedProject("kotlinx.coroutines", "kotlin") + +//sampleStart + // Specifies the base type Project, which includes the `type` discriminator in the output. + println(Json.encodeToString(data)) + // {"type":"OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +#### Define custom serial names for subclasses + +A value of the `type` key is a fully qualified class name by default. +You can change this by using the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation on the +corresponding class. This allows you to define stable _serial name_ that remains consistent, +regardless of changes to the class name in the source code: + + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +sealed class Project { + abstract val name: String +} + +// Assigns the custom serial name "owned" to OwnedProject for JSON output +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes the object with the custom `type` key "owned" instead of the class name + println(Json.encodeToString(data)) + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +> Additionally, you can configure JSON to use a different key name for the class discriminator. +> For more information, see the [Specify class discriminator for polymorphism](serialization-json-configuration.md#specify-class-discriminator-for-polymorphism) section. +> +{type="tip"} + +#### Base class properties with backing fields + +In a sealed hierarchy, the base class can define properties with backing fields that are serialized along with subclass properties: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +sealed class Project { + abstract val name: String + // Defines a property with a backing field in the base class + var status = "open" +} + +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + // Includes default values like "status" + val json = Json { encodeDefaults = true } + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes superclass properties before subclass properties + println(json.encodeToString(data)) + // {"type":"owned","status":"open","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +#### Serialize objects in sealed hierarchies + +Sealed hierarchies can have objects as their subclasses, and these objects must also be annotated with `@Serializable`. +When serialized, objects are treated as empty classes, and their class name is used as the type by default: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +sealed class Response + +// Declares an object that extends Response +@Serializable +object EmptyResponse : Response() + +// Declares a class that extends Response +@Serializable +class TextResponse(val text: String) : Response() + +// Serializes a list of different responses +fun main() { + val list = listOf(EmptyResponse, TextResponse("OK")) + println(Json.encodeToString(list)) + // [{"type":"EmptyResponse"},{"type":"TextResponse","text":"OK"}] +} +``` +{kotlin-runnable="true"} + + + +> The properties of objects are not serialized. +> +{type="note"} + + + + + +## Open polymorphism in Kotlin serialization + +Kotlin Serialization supports polymorphism with `open` and `abstract` classes. +In open polymorphism, subclasses can be defined anywhere in the codebase, even in other modules. +As a result, the list of subclasses cannot be determined at compile time and must be explicitly specified at runtime. + +### Serialize open polymorphic classes + +To serialize and deserialize instances of open polymorphic classes, you need to provide all subclasses in a `SerializersModule` class. + +You can use the [`SerializersModule {}`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html) builder function to create an instance of the `SerializersModule`. +Within this module, specify the base class in the [`polymorphic()`] builder function and specify each subclass with the [`subclass()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html) function. +This module is then passed to the `Json` configuration: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* + +//sampleStart +// Defines a SerializersModule with polymorphic serialization +val module = SerializersModule { + polymorphic(Project::class) { + // Specifies OwnedProject as a subclass of Project + subclass(OwnedProject::class) + } +} + +// Creates a custom JSON format with the module +val format = Json { serializersModule = module } + +// Defines an abstract serializable class Project with an abstract property `name` +@Serializable +abstract class Project { + abstract val name: String +} + +// Defines a subclass OwnedProject with an additional property `owner` and a SerialName +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes data using the custom format + println(format.encodeToString(data)) + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +> For more information about custom JSON configurations, see the [JSON configuration](serialization-json-configuration.md) section. +> +{type="tip"} + + + +This configuration achieves the same effect as [using sealed classes for closed polymorphic classes](#serialize-closed-polymorphic-classes), +but allows the serialization of open polymorphic classes. + + + + + +> This example works only on the JVM because of `serializer()` function restrictions. +> For JS and Native platforms, use an explicit serializer: `format.encodeToString(PolymorphicSerializer(Project::class), data)` +> You can keep track of this issue [here](https://github.com/Kotlin/kotlinx.serialization/issues/1077). +> +{type="note"} + +### Serialize interfaces + +Although you cannot annotate an interface with `@Serializable`, interfaces are implicitly serializable with `PolymorphicSerializer`. +To do so, mark the classes that implement the interface as `@Serializable` and specify them in a `SerializersModule`: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* + +//sampleStart +// Creates a SerializersModule to specify the implementing classes of the interface +val module = SerializersModule { + polymorphic(Project::class) { + subclass(OwnedProject::class) + } +} + +val format = Json { serializersModule = module } + +// Declares an interface used for polymorphic serialization +interface Project { + val name: String +} + +// OwnedProject implements the Project interface +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project + +fun main() { + // Declares `data` with the type of `Project`, which is assigned an instance of `OwnedProject` + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + println(format.encodeToString(data)) + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd +``` + + + + + +> For Kotlin/Native, you need to explicitly specify the serializer using +> `format.encodeToString(PolymorphicSerializer(Project::class), data))` due to the platform's limited reflection capabilities. +> +{type="note"} + + + +Additionally, you can use an interface as a property in a serializable class: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* + +val module = SerializersModule { + polymorphic(Project::class) { + subclass(OwnedProject::class) + } +} + +val format = Json { serializersModule = module } + +//sampleStart +interface Project { + val name: String +} + +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project + +// Data class contains a property of type Project, which is an interface +@Serializable +class Data(val project: Project) // Project is an interface + +fun main() { + val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) + println(format.encodeToString(data)) + // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} +} +//sampleEnd +``` + + + + + + + +### Static parent type lookup for polymorphism + +During serialization of a polymorphic class the root type of the polymorphic hierarchy (`Project` in our example) +is determined statically. Let us take the example with the serializable `abstract class Project`, +but change the `main` function to declare `data` as having a type of `Any`: + + + +```kotlin +fun main() { + val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") + println(format.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-poly-12.kt). + +We get the exception. + +```text +Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. +Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. +``` + + + +We have to register classes for polymorphic serialization with respect for the corresponding static type we +use in the source code. First of all, we change our module to register a subclass of `Any`: + + + +```kotlin +val module = SerializersModule { + polymorphic(Any::class) { + subclass(OwnedProject::class) + } +} +``` + + + +Then we can try to serialize the variable of type `Any`: + +```kotlin +fun main() { + val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") + println(format.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-poly-13.kt). + +However, `Any` is a class and it is not serializable: + +```text +Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. +Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. +``` + + + +We must to explicitly pass an instance of [PolymorphicSerializer] for the base class `Any` as the +first parameter to the [encodeToString][Json.encodeToString] function. + + + +```kotlin +fun main() { + val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") + println(format.encodeToString(PolymorphicSerializer(Any::class), data)) +} +``` + +> You can get the full code [here](../../guide/example/example-poly-14.kt). + +With the explicit serializer it works as before. + +```text +{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +``` + + + +### Explicitly marking polymorphic class properties + +The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism. +However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type. +If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization +strategy via the [`@Serializable`][Serializable] annotation as we saw in +the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section. +To specify a polymorphic serialization strategy of a property, the special-purpose [`@Polymorphic`][Polymorphic] +annotation is used. + + + +```kotlin +@Serializable +class Data( + @Polymorphic // the code does not compile without it + val project: Any +) + +fun main() { + val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) + println(format.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-poly-15.kt). + + + +### Registering multiple superclasses + +When the same class gets serialized as a value of properties with different compile-time type from the list of +its superclasses, we must register it in the [SerializersModule] for each of its superclasses separately. +It is convenient to extract registration of all the subclasses into a separate function and +use it for each superclass. You can use the following template to write it. + + + +```kotlin +val module = SerializersModule { + fun PolymorphicModuleBuilder.registerProjectSubclasses() { + subclass(OwnedProject::class) + } + polymorphic(Any::class) { registerProjectSubclasses() } + polymorphic(Project::class) { registerProjectSubclasses() } +} +``` + + + +> You can get the full code [here](../../guide/example/example-poly-16.kt). + + + +### Polymorphism and generic classes + +Generic subtypes for a serializable class require a special handling. Consider the following hierarchy. + + + +```kotlin +@Serializable +abstract class Response + +@Serializable +@SerialName("OkResponse") +data class OkResponse(val data: T) : Response() +``` + +Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the +type parameter `T` when serializing a property of the polymorphic type `OkResponse`. We have to provide this +strategy explicitly when defining the serializers module for `Response`. In the below example we +use `OkResponse.serializer(...)` to retrieve +the [Plugin-generated generic serializer](serializers.md#plugin-generated-generic-serializer) of +the `OkResponse` class and instantiate it with the [PolymorphicSerializer] instance with +`Any` class as its base. This way, we can serialize an instance of `OkResponse` with any `data` property that +was polymorphically registered as a subtype of `Any`. + +```kotlin +val responseModule = SerializersModule { + polymorphic(Response::class) { + subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) + } +} +``` + +### Merging library serializers modules + +When the application grows in size and splits into source code modules, +it may become inconvenient to store all class hierarchies in one serializers module. +Let us add a library with the `Project` hierarchy to the code from the previous section. + +```kotlin +val projectModule = SerializersModule { + fun PolymorphicModuleBuilder.registerProjectSubclasses() { + subclass(OwnedProject::class) + } + polymorphic(Any::class) { registerProjectSubclasses() } + polymorphic(Project::class) { registerProjectSubclasses() } +} +``` + + + +We can compose those two modules together using the [plus] operator to merge them, +so that we can use them both in the same [Json] format instance. + +> You can also use the [include][SerializersModuleBuilder.include] function +> in the [SerializersModule {}][SerializersModule()] DSL. + +```kotlin +val format = Json { serializersModule = projectModule + responseModule } +```` + +Now classes from both hierarchies can be serialized together and deserialized together. + +```kotlin +fun main() { + // both Response and Project are abstract and their concrete subtypes are being serialized + val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) + val string = format.encodeToString(data) + println(string) + println(format.decodeFromString>(string)) +} + +``` + +> You can get the full code [here](../../guide/example/example-poly-17.kt). + +The JSON that is being produced is deeply polymorphic. + +```text +{"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} +OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) +``` + + + +If you're writing a library or shared module with an abstract class and some implementations of it, +you can expose your own serializers module for your clients to use so that a client can combine your +module with their modules. + +### Default polymorphic type handler for deserialization + +What happens when we deserialize a subclass that was not registered? + + + +```kotlin +fun main() { + println(format.decodeFromString(""" + {"type":"unknown","name":"example"} + """)) +} +``` + +> You can get the full code [here](../../guide/example/example-poly-18.kt). + +We get the following exception. + +```text +Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $ +Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule. +``` + + + +When reading a flexible input we might want to provide some default behavior in this case. For example, +we can have a `BasicProject` subtype to represent all kinds of unknown `Project` subtypes. + + + +```kotlin +@Serializable +abstract class Project { + abstract val name: String +} + +@Serializable +data class BasicProject(override val name: String, val type: String): Project() + +@Serializable +@SerialName("OwnedProject") +data class OwnedProject(override val name: String, val owner: String) : Project() +``` + +We register a default deserializer handler using the [`defaultDeserializer`][PolymorphicModuleBuilder.defaultDeserializer] function in +the [`polymorphic { ... }`][PolymorphicModuleBuilder] DSL that defines a strategy which maps the `type` string from the input +to the [deserialization strategy][DeserializationStrategy]. In the below example we don't use the type, +but always return the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) +of the `BasicProject` class. + +```kotlin +val module = SerializersModule { + polymorphic(Project::class) { + subclass(OwnedProject::class) + defaultDeserializer { BasicProject.serializer() } + } +} +``` + +Using this module we can now deserialize both instances of the registered `OwnedProject` and +any unregistered one. + +```kotlin +val format = Json { serializersModule = module } + +fun main() { + println(format.decodeFromString>(""" + [ + {"type":"unknown","name":"example"}, + {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} + ] + """)) +} +``` + +> You can get the full code [here](../../guide/example/example-poly-19.kt). + +Notice, how `BasicProject` had also captured the specified type key in its `type` property. + +```text +[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)] +``` + + + +We used a plugin-generated serializer as a default serializer, implying that +the structure of the "unknown" data is known in advance. In a real-world API it's rarely the case. +For that purpose a custom, less-structured serializer is needed. You will see the example of such serializer in the future section +on [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attributes). + +### Default polymorphic type handler for serialization + +Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you +don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can register a default serializer. + + + +```kotlin +interface Animal { +} + +interface Cat : Animal { + val catType: String +} + +interface Dog : Animal { + val dogType: String +} + +private class CatImpl : Cat { + override val catType: String = "Tabby" +} + +private class DogImpl : Dog { + override val dogType: String = "Husky" +} + +object AnimalProvider { + fun createCat(): Cat = CatImpl() + fun createDog(): Dog = DogImpl() +} +``` + +We register a default serializer handler using the [`polymorphicDefaultSerializer`][SerializersModuleBuilder.polymorphicDefaultSerializer] function in +the [`SerializersModule { ... }`][SerializersModuleBuilder] DSL that defines a strategy which takes an instance of the base class and +provides a [serialization strategy][SerializationStrategy]. In the below example we use a `when` block to check the type of the +instance, without ever having to refer to the private implementation classes. + +```kotlin +val module = SerializersModule { + polymorphicDefaultSerializer(Animal::class) { instance -> + @Suppress("UNCHECKED_CAST") + when (instance) { + is Cat -> CatSerializer as SerializationStrategy + is Dog -> DogSerializer as SerializationStrategy + else -> null + } + } +} + +object CatSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Cat") { + element("catType") + } + + override fun serialize(encoder: Encoder, value: Cat) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.catType) + } + } +} + +object DogSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Dog") { + element("dogType") + } + + override fun serialize(encoder: Encoder, value: Dog) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.dogType) + } + } +} +``` + +Using this module we can now serialize instances of `Cat` and `Dog`. + +```kotlin +val format = Json { serializersModule = module } -In serialization, closed polymorphism ensures that all possible subclasses are known and handled, providing a clear and -safe way to serialize and deserialize polymorphic data structures. +fun main() { + println(format.encodeToString(AnimalProvider.createCat())) +} +``` -### Static types +> You can get the full code [here](../../guide/example/example-poly-20.kt) -Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined -by *compile-time* types of objects. Let's examine this aspect in more detail and learn how -to serialize polymorphic data structures, where the type of data is determined at runtime. +```text +{"type":"Cat","catType":"Tabby"} +``` -To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project` -has just the `name` property, while its derived `class OwnedProject` adds an `owner` property. -In the below example, we serialize `data` variable with a static type of -`Project` that is initialized with an instance of `OwnedProject` at runtime. -## Open polymorphism \ No newline at end of file + diff --git a/docs/topics/serialization-serialize-builtin-types.md b/docs/topics/serialization-serialize-builtin-types.md new file mode 100644 index 0000000000..2188a69148 --- /dev/null +++ b/docs/topics/serialization-serialize-builtin-types.md @@ -0,0 +1,50 @@ +[//]: # (title: Serialize built-in types) + +The `kotlinx.serialization` library provides support for various built-in types, including primitives, composite types, +and some standard library classes. +The following sections describe the various types that can be serialized and provide examples to demonstrate their usage. + +## Primitive types + +Kotlin serialization supports the following primitive types: `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `Char`, `String`, and `enums`. + +For example, here’s how a `Long` is serialized: + +### Numbers + +You can serialize all types of integer and floating-point Kotlin numbers. + +For example: + + + +```kotlin +@Serializable +class Data( + val answer: Int, + val pi: Double +) + +fun main() { + val data = Data(42, PI) + println(Json.encodeToString(data)) + // {"answer":42,"pi":3.141592653589793} +} +``` + + + +Their natural representation in JSON is used. + + + + diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md index 45010fc3f0..ceb677f8f1 100644 --- a/docs/topics/serialization-transform-json.md +++ b/docs/topics/serialization-transform-json.md @@ -24,17 +24,18 @@ import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* ``` -## Array wrapping +## Wrap a single object in an array during deserialization -The first example is an implementation of JSON array wrapping for lists. +When working with JSON data, you might encounter the same property having different structures, such as being either a single object or an array. +To ensure consistent handling of these variations during deserialization, a custom serializer can be used to wrap single objects in an array, +allowing seamless deserialization into [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/) types. -Consider a REST API that returns a JSON array of `User` objects, or a single object (not wrapped into an array) if there -is only one element in the result. +Let's look at an example, where the `Project` class has a `users` property that expects a list of objects. +Although the input can either be a single object or an array, the goal is to ensure both are consistently deserialized into a `List`. +You can use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in the data model to specify a custom serializer for the +`users: List` property: -In the data model, use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation to specify a custom serializer for a -`users: List` property. - - -The output shows that both cases are correctly deserialized into a Kotlin [List]. - -## Array unwrapping +## Unwrap a single-element array during serialization -You can also implement the `transformSerialize()` function to unwrap a single-element list into a single JSON object +You can use the `transformSerialize()` function to unwrap a single-element list into a single JSON object during serialization: - -```kotlin override fun transformSerialize(element: JsonElement): JsonElement { - require(element is JsonArray) // this serializer is used only with lists + // Ensures that the input is a list + require(element is JsonArray) + // Unwraps single-element lists into a single JSON object return element.singleOrNull() ?: element } -``` - - - -Now, if you serialize a single-element list of objects from Kotlin: - -```kotlin + fun main() { val data = Project("kotlinx.serialization", listOf(User("kotlin"))) println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","users":{"name":"kotlin"}} } ``` -> You can get the full code [here](../../guide/example/example-json-transform-02.kt). - -You end up with a single JSON object, not an array with one element: + + -## Manipulate default values +## Omit specific properties during serialization -Another kind of useful transformation is omitting specific values from the output JSON, for example, if it -is used as default when missing or for other reasons. +You can omit properties from the JSON output when they have default values, match specific values, or when they are missing. +This helps streamline the data, reducing unnecessary information while ensuring that only relevant properties are serialized. -Imagine that you cannot specify a default value for the `language` property in the `Project` data model for some reason, -but you need it omitted from the JSON when it is equal to `Kotlin` (we can all agree that Kotlin should be default anyway). -You can fix it by writing the special `ProjectSerializer` based on -the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) for the `Project` class. +Let's look at an example where the `Project` class has a `language` property that should be omitted from the JSON output when its value is `"Kotlin"`. +To do this, you can write a custom `ProjectSerializer` based on the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) for the `Project` class: + +In the example below, we are serializing the `Project` class at the top-level, so we explicitly +pass the above `ProjectSerializer` to [Json.encodeToString] function as was shown in +the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: ```kotlin @Serializable class Project(val name: String, val language: String) +// Custom serializer that omits the "language" property if it is equal to "Kotlin" object ProjectSerializer : JsonTransformingSerializer(Project.serializer()) { override fun transformSerialize(element: JsonElement): JsonElement = - // Filter out top-level key value pair with the key "language" and the value "Kotlin" + // Omits the "language" property if its value is "Kotlin" JsonObject(element.jsonObject.filterNot { (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin" }) } -``` - -In the example below, we are serializing the `Project` class at the top-level, so we explicitly -pass the above `ProjectSerializer` to [Json.encodeToString] function as was shown in -the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: -```kotlin fun main() { val data = Project("kotlinx.serialization", "Kotlin") println(Json.encodeToString(data)) // using plugin-generated serializer - println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer + // {"name":"kotlinx.serialization","language":"Kotlin"} + println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer + // {"name":"kotlinx.serialization"} } ``` -> You can get the full code [here](../../guide/example/example-json-transform-03.kt). +> When serializing an object directly, you need to explicitly pass the custom serializer to the `Json.encodeToString()` +> function to ensure that the custom serialization logic is applied. For more information, see the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section. +> +{type="note"} -See the effect of the custom serializer: + + -### Content-based polymorphic deserialization - -Typically, [polymorphic serialization](polymorphism.md) requires a dedicated `"type"` key -(also known as _class discriminator_) in the incoming JSON object to determine the actual serializer -which should be used to deserialize Kotlin class. +## Content-based polymorphic deserialization -However, sometimes the `type` property may not be present in the input. In this case, you need to guess -the actual type by the shape of JSON, for example by the presence of a specific key. +In [polymorphic serialization](polymorphism.md), a _class discriminator_, a dedicated `"type"` property in the JSON, +is usually included to determine which serializer should be used to deserialize the Kotlin class. -[JsonContentPolymorphicSerializer] provides a skeleton implementation for such a strategy. -To use it, override its `selectDeserializer` method. -Let's start with the following class hierarchy. +When no class discriminator is present in the JSON input, you can use [`JsonContentPolymorphicSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/) to infer the type from the structure of the JSON. +This serializer allows you to override the `selectDeserializer()` function to choose the correct deserializer based on the JSON content. -> Note that is does not have to be `sealed` as recommended in the [Sealed classes](polymorphism.md#sealed-classes) section, -> because we are not going to take advantage of the plugin-generated code that automatically selects the -> appropriate subclass, but are going to implement this code manually. +> When you use this serializer, the appropriate deserializer is chosen at runtime. +> It can either come from the [registered](polymorphism.md#registered-subclasses) or the default serializer. +> +{type="tip"} + -### Under the hood (experimental) +## Under the hood (experimental) Although abstract serializers mentioned above can cover most of the cases, it is possible to implement similar machinery -manually, using only the [KSerializer] class. -If tweaking the abstract methods `transformSerialize`/`transformDeserialize`/`selectDeserializer` is not enough, -then altering `serialize`/`deserialize` is a way to go. +manually, using only the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) class. +If tweaking the abstract methods `transformSerialize()`/`transformDeserialize()`/`selectDeserializer()` is not enough, +then altering `serialize()`/`deserialize()` is a way to go. Here are some useful things about custom serializers with [Json]: -* [Encoder] can be cast to [JsonEncoder], and [Decoder] to [JsonDecoder], if the current format is [Json]. -* `JsonDecoder` has the [decodeJsonElement][JsonDecoder.decodeJsonElement] method and `JsonEncoder` - has the [encodeJsonElement][JsonEncoder.encodeJsonElement] method, +* `Encoder` can be cast to `JsonEncoder`, and `Decoder` to `JsonDecoder`, if the current format is `Json`. +* `JsonDecoder` has the [`decodeJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html) function and `JsonEncoder` + has the [`encodeJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html) function, which basically retrieve an element from and insert an element to a current position in the stream. -* Both [`JsonDecoder`][JsonDecoder.json] and [`JsonEncoder`][JsonEncoder.json] have the `json` property, - which returns [Json] instance with all settings that are currently in use. -* [Json] has the [encodeToJsonElement][Json.encodeToJsonElement] and [decodeFromJsonElement][Json.decodeFromJsonElement] methods. +* Both `JsonDecoder` and `JsonEncoder` have the `json` property, + which returns `Json` instance with all settings that are currently in use. +* `Json` has the [`encodeToJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html) and [`decodeFromJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) functions. Given all that, it is possible to implement two-stage conversion `Decoder -> JsonElement -> value` or `value -> JsonElement -> Encoder`. @@ -331,6 +322,7 @@ class ResponseSerializer(private val dataSerializer: KSerializer) : KSeria Having this serializable `Response` implementation, you can take any serializable payload for its data and serialize or deserialize the corresponding responses: +This gives you fine-grained control on the representation of the `Response` class in the JSON output: ```kotlin @Serializable @@ -343,43 +335,41 @@ fun main() { ) val string = Json.encodeToString(responses) println(string) + // [{"name":"kotlinx.serialization"},{"error":"Not found"}] println(Json.decodeFromString>>(string)) + // [Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)] } ``` -> You can get the full code [here](../../guide/example/example-json-transform-05.kt). + -This gives you fine-grained control on the representation of the `Response` class in the JSON output: + + -### Maintaining custom JSON attributes +## Maintain custom JSON attributes -A good example of custom JSON-specific serializer would be a deserializer -that packs all unknown JSON properties into a dedicated field of `JsonObject` type. +When JSON input contains properties that aren't predefined in your class, you can use a custom deserializer to capture these unknown properties. +By storing them in a `JsonObject`, you ensure that they are preserved during deserialization. -Let's add `UnknownProject` – a class with the `name` property and arbitrary details flattened into the same object: +If your class defines a set of known fields, but you need to capture any additional unknown fields, you can create a custom serializer. +This serializer can store the unknown fields in a separate field, which ensures that they are preserved during deserialization. +The unknown fields are flattened, meaning they are kept in the same object as the known fields, without being nested inside a separate structure: - -```kotlin data class UnknownProject(val name: String, val details: JsonObject) -``` -However, the default plugin-generated serializer requires details -to be a separate JSON object and that's not what we want. - -To mitigate that, write an own serializer that uses the fact that it works only with the `Json` format: - -```kotlin object UnknownProjectSerializer : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") { element("name") @@ -387,12 +377,16 @@ object UnknownProjectSerializer : KSerializer { } override fun deserialize(decoder: Decoder): UnknownProject { - // Cast to JSON-specific interface + // Ensures the decoder is JSON-specific val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") - // Read the whole content as JSON + + // Reads the entire content as JSON val json = jsonInput.decodeJsonElement().jsonObject - // Extract and remove name property + + // Extracts and removes the "name" property val name = json.getValue("name").jsonPrimitive.content + + // Flattens the remaining properties into the 'details' field val details = json.toMutableMap() details.remove("name") return UnknownProject(name, JsonObject(details)) @@ -402,20 +396,21 @@ object UnknownProjectSerializer : KSerializer { error("Serialization is not supported") } } -``` - -Now it can be used to read flattened JSON details as `UnknownProject`: -```kotlin fun main() { + // Deserializes JSON with unknown fields into 'UnknownProject' println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}""")) + // UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"}) + } ``` -> You can get the full code [here](../../guide/example/example-json-transform-06.kt). + + diff --git a/docs/topics/serialization.md b/docs/topics/serialization.md index 655e3ae6a9..329d9ef38c 100644 --- a/docs/topics/serialization.md +++ b/docs/topics/serialization.md @@ -51,10 +51,6 @@ Use the same dependencies in JVM, JS, Native, and multiplatform projects. Note that the `kotlinx.serialization` libraries use their own versioning structure, which doesn't match Kotlin's versioning. Check out the releases on [GitHub](https://github.com/Kotlin/kotlinx.serialization/releases) to find the latest versions. -## The @Serializable annotation - -[TBD] - ## Supported formats `kotlinx.serialization` includes libraries for various serialization formats: @@ -76,15 +72,11 @@ For more details about the available serialization formats, see [Serialization f Kotlin serialization supports the following built-in primitive types: * `Boolean` -* `Byte` -* `Short` -* `Int` -* `Long` -* `Float` -* `Double` * `Char` +* Integer types: `Byte`, `Short`, `Int`, and `Long` +* Floating-point types: `Float` and `Double` * `String` -* `enums` +* `enum` In addition to primitives Kotlin serialization also supports a number of composite types: @@ -96,15 +88,18 @@ These types are represented as lists in formats like JSON. A JSON list can be de * Duration: Since Kotlin 1.7.20, the [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) class of the `kotlin.time` package is serializable. It is serialized as a string in the ISO-8601-2 format. For example, "PT16M40S" is 16 minutes and 40 seconds. * Nothing: The [`Nothing`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html) type is also serializable. However, since there are no instances of this class, it is impossible to encode or decode its values. This serializer is used when syntactically some type is needed, but it is not actually used in serialization. -> Not all classes from the Kotlin standard library are serializable. In particular, ranges and the [`Regex`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/) class are not serializable at the moment. +> Not all types from the Kotlin standard library are serializable. In particular, [ranges](ranges.md) and the [`Regex`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/) class are not serializable at the moment. > Support for their serialization may be added in the future. > {type="note"} -If you would like to see code examples for the supported types, see the GitHub repository with [Built-in types examples](https://github.com/Kotlin/kotlinx.serialization/tree/master/guide/example). +Additionally, classes annotated with `@Serializable` are fully supported for serialization, enabling the conversion of class instances to and from formats like JSON. +For more information, see [Serializable types](serialization-customization-options.md). + +For more details and examples about the supported built-in types, see [Serialization GitHub repository](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/builtin-classes.md). ## What's next * Learn the basics of Kotlin serialization in the [Get started with serialization guide](serialization-get-started-overview.md). -* To explore more complex scenarios of JSON serialization, see Advanced JSON Serialization Techniques. -* Delve into Custom JSON Configuration to learn how to create and configure custom formats, write your own serializers, and optimize performance for specific use cases. +* To explore more complex scenarios of JSON serialization, see [JSON serialization overview](configure-json-serialization.md). +* Dive into the [Serializable classes](serialization-customization-options.md) section to learn how to modify the default behavior of the `@Serializable` annotation. From 5580675a70d5e37c134cbfb529f1c202c676cca8 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 26 Sep 2024 16:15:04 +0200 Subject: [PATCH 07/14] polymorphism done starting custom serializers --- docs/topics/create-custom-serializers.md | 917 +++++++++++++++++- .../serialization-customization-options.md | 116 ++- docs/topics/serialization-get-started.md | 1 + .../serialization-json-configuration.md | 4 +- docs/topics/serialization-polymorphism.md | 582 +++++------ 5 files changed, 1272 insertions(+), 348 deletions(-) diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index c4ce52d68e..f76e3e7938 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -1,29 +1,928 @@ [//]: # (title: Create custom serializers) -A plugin-generated serializer is convenient, but it may not produce the JSON we want for classes such as `Color`. -In this section we will cover the alternatives. + -## Primitive serializer +Formats like JSON control how an object is encoded into bytes, but a serializer determines how the object’s properties are represented. +Automatically generated serializers with the `@Serializable` annotation can handle most common use cases, making it easy to serialize Kotlin objects without additional configuration. +The Kotlin Serialization plugin automatically generates a `KSerializer` implementation for each class annotated with `@Serializable`, +which you can retrieve using the `.serializer()` function: -We want to serialize the `Color` class as a hex string with the green color represented as `"00ff00"`. -To achieve this, we write an object that implements the `KSerializer` interface for the `Color` class. +```kotlin +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* + +//sampleStart +@Serializable +data class Color(val rgb: Int) + +fun main() { + val colorSerializer: KSerializer = Color.serializer() + println(colorSerializer.descriptor) + // Color(rgb: kotlin.Int) +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +However, if you need to customize how your data is encoded and decoded, such as converting an RGB value into a hexadecimal string and back, +you can create a custom serializer. +Custom serializers implement the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) interface and +allowing you to control how your data is transformed between its Kotlin object form and its serialized form. + +To create a custom serializer: + +1. Use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation with the [`with`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/with.html) property to bind a custom serializer to a class. The `with` property specifies the serializer used for the bound class. +2. Create an `object` that implements the `KSerializer` interface. This allows you to define how instances of your class are serialized and deserialized. +3. Override the [`descriptor`]((https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/descriptor.html)) property to define the schema for the serialized data. Use the [`PrimitiveSerialDescriptor()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-serial-descriptor.html) function to specify the name and type of the data, such as string, integer, or custom structure. +4. Implement the [`serialize()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/serialize.html) function to define how to convert an instance of your class into its serialized form. The [`Encoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/) provides functions to write different data types, such as [`encodeString()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-string.html). +5. Implement the [`deserialize()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/deserialize.html) function to define how to convert the serialized data back into an instance of your class. The [`Decoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/) provides functions to read the data, such as [`decodeString()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-string.html). + +Let's look at an example: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* + +//sampleStart +// Binds the Color class with the custom ColorAsStringSerializer using the with property +@Serializable(with = ColorAsStringSerializer::class) +data class Color(val rgb: Int) + +// Creates the custom serializer for the Color class object ColorAsStringSerializer : KSerializer { + // Defines the schema for the serialized data as a single string override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) + // Defines how the Color object is converted to a string during serialization + override fun serialize(encoder: Encoder, value: Color) { + // Converts the RGB value to a hex string + val hexValue = value.rgb.toString(16).padStart(6, '0') + // Encodes the hex string using the encodeString() function + encoder.encodeString(hexValue) + } + + // Defines how the string is converted back to a Color object during deserialization + override fun deserialize(decoder: Decoder): Color { + // Decodes the string using the decodeString() function + val hexValue = decoder.decodeString() + // Converts the hex value back into a Color object + return Color(hexValue.toInt(16)) + } +} + +fun main() { + val color = Color(0x00FF00) + // Serializes a color to JSON + val jsonString = Json.encodeToString(color) + println(jsonString) + // "00ff00" + + // Deserializes the color back + val deserializedColor = Json.decodeFromString(jsonString) + println(deserializedColor.rgb) + // 65280 +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +## Delegate serialization to another serializer + +To serialize a class as a different type, you can delegate the serialization logic to another serializer. +This approach allows you to transform the data of your class, like breaking down an `Int` into an `IntArray` of its individual bytes, +and then delegate the actual encoding and decoding to the appropriate serializer. + +To delegate the serialization logic, create a custom serializer that uses another serializer as a delegate. +Within the `serialize()` and `deserialize()` functions, you can transform your class’s data and pass it to the delegate +serializer with the [`encodeSerializableValue()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html) and [`decodeSerializableValue()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html) functions, respectively: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.builtins.IntArraySerializer +import kotlinx.serialization.json.* + +//sampleStart +// Creates a custom serializer that delegates to IntArraySerializer +class ColorIntArraySerializer : KSerializer { + private val delegateSerializer = IntArraySerializer() + override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor) + + // Delegates serialization logic to IntArraySerializer + override fun serialize(encoder: Encoder, value: Color) { + val data = intArrayOf( + (value.rgb shr 16) and 0xFF, + (value.rgb shr 8) and 0xFF, + value.rgb and 0xFF + ) + encoder.encodeSerializableValue(delegateSerializer, data) + } + + // Delegates deserialization logic to IntArraySerializer + override fun deserialize(decoder: Decoder): Color { + val array = decoder.decodeSerializableValue(delegateSerializer) + return Color((array[0] shl 16) or (array[1] shl 8) or array[2]) + } +} + +@Serializable(with = ColorIntArraySerializer::class) +class Color(val rgb: Int) + +fun main() { + val green = Color(0x00ff00) + println(Json.encodeToString(green)) + // [0,255,0] +} +//sampleEnd +``` +{kotlin-runnable="true"} + +This array representation may not be ideal for JSON, but it becomes highly efficient when used with a `ByteArray` and binary formats, where it can significantly reduce data size. +For more information on how format can treat arrays, see the [Format specific types](formats.md#format-specific-types) section. + +> When using delegated serialization, you cannot rely on the default class serializer’s `descriptor`. +> Formats that depend on schemas may incorrectly assume you are encoding a primitive type, like `Int`, +> and expect `encodeInt()` to be called instead of the [`encodeSerializableValue()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html) function. +> Similarly, using a specific serializer’s `descriptor` directly can lead to confusion in formats that treat certain types specially, like arrays. +> +{type="note"} + + + + + + + +### Composite serializer via surrogate + +Now our challenge is to get `Color` serialized so that it is represented in JSON as if it is a class +with three properties—`r`, `g`, and `b`—so that JSON encodes it as an object. +The easiest way to achieve this is to define a _surrogate_ class mimicking the serialized form of `Color` that +we are going to use for its serialization. We also set the [SerialName] of this surrogate class to `Color`. Then if +any format uses this name the surrogate looks like it is a `Color` class. +The surrogate class can be `private`, and can enforce all the constraints on the serial representation +of the class in its `init` block. + +```kotlin +@Serializable +@SerialName("Color") +private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { + init { + require(r in 0..255 && g in 0..255 && b in 0..255) + } +} +``` + +> An example of where the class name is used is shown in +> the [Custom subclass serial name](polymorphism.md#custom-subclass-serial-name) section in the chapter on polymorphism. + +Now we can use the `ColorSurrogate.serializer()` function to retrieve a plugin-generated serializer for the +surrogate class. + +We can use the same approach as in [delegating serializer](#delegating-serializers), but this time, +we are fully reusing an automatically +generated [SerialDescriptor] for the surrogate because it should be indistinguishable from the original. + +```kotlin +object ColorSerializer : KSerializer { + override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor + override fun serialize(encoder: Encoder, value: Color) { - val string = value.rgb.toString(16).padStart(6, '0') - encoder.encodeString(string) + val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) + encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) } override fun deserialize(decoder: Decoder): Color { - val string = decoder.decodeString() - return Color(string.toInt(16)) + val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) + return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) + } +} +``` + +We bind the `ColorSerializer` serializer to the `Color` class. + +```kotlin +@Serializable(with = ColorSerializer::class) +class Color(val rgb: Int) +``` + +Now we can enjoy the result of serialization for the `Color` class. + + + + + + + + + +### Hand-written composite serializer + +There are some cases where a surrogate solution does not fit. Perhaps we want to avoid the performance +implications of additional allocation, or we want a configurable/dynamic set of properties for the +resulting serial representation. In these cases we need to manually write a class +serializer which mimics the behaviour of a generated serializer. + +```kotlin +object ColorAsObjectSerializer : KSerializer { +``` + +Let's introduce it piece by piece. First, a descriptor is defined using the [buildClassSerialDescriptor] builder. +The [element][ClassSerialDescriptorBuilder.element] function in the builder DSL automatically fetches serializers +for the corresponding fields by their type. The order of elements is important. They are indexed starting from zero. + +```kotlin + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Color") { + element("r") + element("g") + element("b") + } +``` + +> The "element" is a generic term here. What is an element of a descriptor depends on its [SerialKind]. +> Elements of a class descriptor are its properties, elements of a enum descriptor are its cases, etc. + +Then we write the `serialize` function using the [encodeStructure] DSL that provides access to +the [CompositeEncoder] in its block. The difference between [Encoder] and [CompositeEncoder] is the latter +has `encodeXxxElement` functions that correspond to the `encodeXxx` functions of the former. They must be called +in the same order as in the descriptor. + +```kotlin + override fun serialize(encoder: Encoder, value: Color) = + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) + encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) + encodeIntElement(descriptor, 2, value.rgb and 0xff) + } +``` + +The most complex piece of code is the `deserialize` function. It must support formats, like JSON, that +can decode properties in an arbitrary order. It starts with the call to [decodeStructure] to +get access to a [CompositeDecoder]. Inside it we write a loop that repeatedly calls +[decodeElementIndex][CompositeDecoder.decodeElementIndex] to decode the index of the next element, then we decode the corresponding +element using [decodeIntElement][CompositeDecoder.decodeIntElement] in our example, and finally we terminate the loop when +`CompositeDecoder.DECODE_DONE` is encountered. + +```kotlin + override fun deserialize(decoder: Decoder): Color = + decoder.decodeStructure(descriptor) { + var r = -1 + var g = -1 + var b = -1 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> r = decodeIntElement(descriptor, 0) + 1 -> g = decodeIntElement(descriptor, 1) + 2 -> b = decodeIntElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + require(r in 0..255 && g in 0..255 && b in 0..255) + Color((r shl 16) or (g shl 8) or b) + } +``` + + + +Now we bind the resulting serializer to the `Color` class and test its serialization/deserialization. + +```kotlin +@Serializable(with = ColorAsObjectSerializer::class) +data class Color(val rgb: Int) + +fun main() { + val color = Color(0x00ff00) + val string = Json.encodeToString(color) + println(string) + require(Json.decodeFromString(string) == color) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-12.kt). + +As before, we got the `Color` class represented as a JSON object with three keys: + +```text +{"r":0,"g":255,"b":0} +``` + + + +### Sequential decoding protocol (experimental) + +The implementation of the `deserialize` function from the previous section works with any format. However, +some formats either always store all the complex data in order, or only do so sometimes (JSON always stores +collections in order). With these formats the complex protocol of calling `decodeElementIndex` in the loop is +not needed, and a faster implementation can be used if the [CompositeDecoder.decodeSequentially] function returns `true`. +The plugin-generated serializers are actually conceptually similar to the below code. + + + +```kotlin + override fun deserialize(decoder: Decoder): Color = + decoder.decodeStructure(descriptor) { + var r = -1 + var g = -1 + var b = -1 + if (decodeSequentially()) { // sequential decoding protocol + r = decodeIntElement(descriptor, 0) + g = decodeIntElement(descriptor, 1) + b = decodeIntElement(descriptor, 2) + } else while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> r = decodeIntElement(descriptor, 0) + 1 -> g = decodeIntElement(descriptor, 1) + 2 -> b = decodeIntElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + require(r in 0..255 && g in 0..255 && b in 0..255) + Color((r shl 16) or (g shl 8) or b) + } +``` + + + +> You can get the full code [here](../../guide/example/example-serializer-13.kt). + + + +### Serializing 3rd party classes + +Sometimes an application has to work with an external type that is not serializable. +Let us use [java.util.Date] as an example. As before, we start by writing an implementation of [KSerializer] +for the class. Our goal is to get a `Date` serialized as a long number of milliseconds following the +approach from the [Primitive serializer](#primitive-serializer) section. + +> In the following sections any kind of `Date` serializer would work. For example, if we want `Date` to be serialized +> as an object, we would use an approach from +> the [Composite serializer via surrogate](#composite-serializer-via-surrogate) section. +> See also [Deriving external serializer for another Kotlin class (experimental)](#deriving-external-serializer-for-another-kotlin-class-experimental) +> when you need to serialize a 3rd-party Kotlin class that could have been serializable, but is not. + + + +```kotlin +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} +``` + +We cannot bind the `DateAsLongSerializer` serializer to the `Date` class with the [`@Serializable`][Serializable] annotation +because we don't control the `Date` source code. There are several ways to work around that. + +### Passing a serializer manually + +All `encodeToXxx` and `decodeFromXxx` functions have an overload with the first serializer parameter. +When a non-serializable class, like `Date`, is the top-level class being serialized, we can use those. + +```kotlin +fun main() { + val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00") + println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-14.kt). + +```text +1455494400000 +``` + + + +### Specifying serializer on a property + +When a property of a non-serializable class, like `Date`, is serialized as part of a serializable class we must supply +its serializer or the code will not compile. This is accomplished using the [`@Serializable`][Serializable] annotation on the property. + + + +```kotlin +@Serializable +class ProgrammingLanguage( + val name: String, + @Serializable(with = DateAsLongSerializer::class) + val stableReleaseDate: Date +) + +fun main() { + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-15.kt). + +The `stableReleaseDate` property is serialized with the serialization strategy that we specified for it: + +```text +{"name":"Kotlin","stableReleaseDate":1455494400000} +``` + + + +### Specifying serializer for a particular type + +[`@Serializable`][Serializable] annotation can also be applied directly to the types. +This is handy when a class that requires a custom serializer, such as `Date`, happens to be a generic type argument. +The most common use case for that is when you have a list of dates: + + + +```kotlin +@Serializable +class ProgrammingLanguage( + val name: String, + val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> +) + +fun main() { + val df = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-16.kt). + +```text +{"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]} +``` + + + +### Specifying serializers for a file + +A serializer for a specific type, like `Date`, can be specified for a whole source code file with the file-level +[UseSerializers] annotation at the beginning of the file. + +```kotlin +@file:UseSerializers(DateAsLongSerializer::class) +``` + + + + + +Now a `Date` property can be used in a serializable class without additional annotations. + +```kotlin +@Serializable +class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) + +fun main() { + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(Json.encodeToString(data)) +} +``` +> You can get the full code [here](../../guide/example/example-serializer-17.kt). + +```text +{"name":"Kotlin","stableReleaseDate":1455494400000} +``` + + + +### Specifying serializer globally using typealias + +kotlinx.serialization tends to be the always-explicit framework when it comes to serialization strategies: normally, +they should be explicitly mentioned in `@Serializable` annotation. Therefore, we do not provide any kind of global serializer +configuration (except for [context serializer](#contextual-serialization) mentioned later). + +However, in projects with a large number of files and classes, it may be too cumbersome to specify `@file:UseSerializers` +every time, especially for classes like `Date` or `Instant` that have a fixed strategy of serialization across the project. +For such cases, it is possible to specify serializers using `typealias`es, as they preserve annotations, including serialization-related ones: + + +```kotlin +typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date + +typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date +``` + +Using these new different types, it is possible to serialize a Date differently without additional annotations: + +```kotlin +@Serializable +class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) + +fun main() { + val format = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-18.kt). + +```text +{"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000} +``` + + + +### Custom serializers for a generic type + +Let us take a look at the following example of the generic `Box` class. +It is marked with `@Serializable(with = BoxSerializer::class)` as we plan to have a custom serialization +strategy for it. + +```kotlin +@Serializable(with = BoxSerializer::class) +data class Box(val contents: T) +``` + +An implementation of [KSerializer] for a regular type is written as an `object`, as we saw in this chapter's +examples for the `Color` type. A generic class serializer is instantiated with serializers +for its generic parameters. We saw this in the [Plugin-generated generic serializer](#plugin-generated-generic-serializer) section. +A custom serializer for a generic class must be a `class` with a constructor that accepts as many [KSerializer] +parameters as the type has generic parameters. Let us write a `Box` serializer that erases itself during +serialization, delegating everything to the underlying serializer of its `data` property. + +```kotlin +class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor = dataSerializer.descriptor + override fun serialize(encoder: Encoder, value: Box) = dataSerializer.serialize(encoder, value.contents) + override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) +} +``` + +Now we can serialize and deserialize `Box`. + +```kotlin +@Serializable +data class Project(val name: String) + +fun main() { + val box = Box(Project("kotlinx.serialization")) + val string = Json.encodeToString(box) + println(string) + println(Json.decodeFromString>(string)) } ``` +> You can get the full code [here](../../guide/example/example-serializer-19.kt). + +The resulting JSON looks like the `Project` class was serialized directly. + +```text +{"name":"kotlinx.serialization"} +Box(contents=Project(name=kotlinx.serialization)) +``` + + + +### Format-specific serializers + +The above custom serializers worked in the same way for every format. However, there might be format-specific +features that a serializer implementation would like to take advantage of. + +* The [Json transformations](json.md#json-transformations) section of the [Json](json.md) chapter provides examples + of serializers that utilize JSON-specific features. + +* A format implementation can have a format-specific representation for a type as explained + in the [Format-specific types](formats.md#format-specific-types) section of + the [Alternative and custom formats (experimental)](formats.md) chapter. + +This chapter proceeds with a generic approach to tweaking the serialization strategy based on the context. + +## Contextual serialization + +All the previous approaches to specifying custom serialization strategies were _static_, that is +fully defined at compile-time. The exception was the [Passing a serializer manually](#passing-a-serializer-manually) +approach, but it worked only on a top-level object. You might need to change the serialization +strategy for objects deep in the serialized object tree at run-time, with the strategy being selected in a context-dependent way. +For example, you might want to represent `java.util.Date` in JSON format as an ISO 8601 string or as a long integer +depending on a version of a protocol you are serializing data for. This is called _contextual_ serialization, and it +is supported by a built-in [ContextualSerializer] class. Usually we don't have to use this serializer class explicitly—there +is the [Contextual] annotation providing a shortcut to +the `@Serializable(with = ContextualSerializer::class)` annotation, +or the [UseContextualSerialization] annotation can be used at the file-level just like +the [UseSerializers] annotation. Let's see an example utilizing the former. + + + +```kotlin +@Serializable +class ProgrammingLanguage( + val name: String, + @Contextual + val stableReleaseDate: Date +) +``` + + + +To actually serialize this class we must provide the corresponding context when calling the `encodeToXxx`/`decodeFromXxx` +functions. Without it we'll get a "Serializer for class 'Date' is not found" exception. + +> See [here](../../guide/example/example-serializer-20.kt) for an example that produces that exception. + + + + + +### Serializers module + +To provide a context, we define a [SerializersModule] instance that describes which serializers shall be used +at run-time to serialize which contextually-serializable classes. This is done using the +[SerializersModule {}][SerializersModule()] builder function, which provides the [SerializersModuleBuilder] DSL to +register serializers. In the below example we use the [contextual][_contextual] function with the serializer. The corresponding +class this serializer is defined for is fetched automatically via the `reified` type parameter. + +```kotlin +private val module = SerializersModule { + contextual(DateAsLongSerializer) +} +``` + +Next we create an instance of the [Json] format with this module using the +[Json {}][Json()] builder function and the [serializersModule][JsonBuilder.serializersModule] property. + +> Details on custom JSON configurations can be found in +> the [JSON configuration](json.md#json-configuration) section. + +```kotlin +val format = Json { serializersModule = module } +``` + +Now we can serialize our data with this `format`. + +```kotlin +fun main() { + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(format.encodeToString(data)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-21.kt). +```text +{"name":"Kotlin","stableReleaseDate":1455494400000} +``` + + + +### Contextual serialization and generic classes + +In the previous section we saw that we can register serializer instance in the module for a class we want to serialize contextually. +We also know that [serializers for generic classes have constructor parameters](#custom-serializers-for-a-generic-type) — type arguments serializers. +It means that we can't use one serializer instance for a class if this class is generic: + +```kotlin +val incorrectModule = SerializersModule { + // Can serialize only Box, but not Box or others + contextual(BoxSerializer(Int.serializer())) +} +``` + +For cases when one want to serialize contextually a generic class, it is possible to register provider in the module: + +```kotlin +val correctModule = SerializersModule { + // args[0] contains Int.serializer() or String.serializer(), depending on the usage + contextual(Box::class) { args -> BoxSerializer(args[0]) } +} +``` + + + +> Additional details on serialization modules are given in +> the [Merging library serializers modules](polymorphism.md#merging-library-serializers-modules) section of +> the [Polymorphism](polymorphism.md) chapter. + +## Deriving external serializer for another Kotlin class (experimental) + +If a 3rd-party class to be serialized is a Kotlin class with a properties-only primary constructor, a kind of +class which could have been made `@Serializable`, then you can generate an _external_ serializer for it +using the [Serializer] annotation on an object with the [`forClass`][Serializer.forClass] property. + +```kotlin +// NOT @Serializable +class Project(val name: String, val language: String) + +@Serializer(forClass = Project::class) +object ProjectSerializer +``` + +You must bind this serializer to a class using one of the approaches explained in this chapter. We'll +follow the [Passing a serializer manually](#passing-a-serializer-manually) approach for this example. + +```kotlin +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(ProjectSerializer, data)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-22.kt). + +This gets all the `Project` properties serialized: + +```text +{"name":"kotlinx.serialization","language":"Kotlin"} +``` + + + +### External serialization uses properties + +As we saw earlier, the regular `@Serializable` annotation creates a serializer so that +[Backing fields are serialized](basic-serialization.md#backing-fields-are-serialized). _External_ serialization using +`Serializer(forClass = ...)` has no access to backing fields and works differently. +It serializes only _accessible_ properties that have setters or are part of the primary constructor. +The following example shows this. + +```kotlin +// NOT @Serializable, will use external serializer +class Project( + // val in a primary constructor -- serialized + val name: String +) { + var stars: Int = 0 // property with getter & setter -- serialized + + val path: String // getter only -- not serialized + get() = "kotlin/$name" + + private var locked: Boolean = false // private, not accessible -- not serialized +} + +@Serializer(forClass = Project::class) +object ProjectSerializer + +fun main() { + val data = Project("kotlinx.serialization").apply { stars = 9000 } + println(Json.encodeToString(ProjectSerializer, data)) +} +``` + +> You can get the full code [here](../../guide/example/example-serializer-23.kt). + +The output is shown below. + +```text +{"name":"kotlinx.serialization","stars":9000} +``` + + + + ### Base64 To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default diff --git a/docs/topics/serialization-customization-options.md b/docs/topics/serialization-customization-options.md index a4ed632f51..2e346cbe75 100644 --- a/docs/topics/serialization-customization-options.md +++ b/docs/topics/serialization-customization-options.md @@ -2,7 +2,7 @@ [//]: # (title: Serialize classes) The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. -However, you can customize this behavior to fit your specific needs. +You can further customize this behavior to fit your specific needs. This section covers how to adjust serialization using various techniques to control which properties are serialized and how the serialization process is managed. @@ -17,12 +17,12 @@ allowing classes to be easily converted to and from formats like JSON. In Kotlin, only properties with backing fields are serialized. This means that properties defined solely by getter/setter methods or delegated properties without backing fields are excluded from serialization: - -```kotlin +//sampleStart @Serializable class Project( // name is a property with backing field -- serialized @@ -45,7 +45,9 @@ fun main() { println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","stars":9000} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -61,8 +63,13 @@ Kotlin Serialization natively supports nullable properties. Like [other defaults](#set-default-values-for-optional-properties), `null` values are not encoded in JSON: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable -// The 'renamedTo' property is nullable and defaults to null, and it's not encoded +// The renamedTo property is nullable and defaults to null, and it's not encoded class Project(val name: String, val renamedTo: String? = null) fun main() { @@ -70,7 +77,9 @@ fun main() { println(Json.encodeToString(data)) // {"name":"kotlinx.serialization"} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -87,6 +96,11 @@ If a `null` value is encountered in a JSON object for a non-nullable Kotlin prop even if the property has a default value, an exception is raised: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable data class Project(val name: String, val language: String = "Kotlin") @@ -97,7 +111,9 @@ fun main() { println(data) // JsonDecodingException } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -119,13 +135,18 @@ In the following example, since the `language` property is specified in the inpu in the output: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart fun computeLanguage(): String { println("Computing") return "Kotlin" } @Serializable -// Initializer is skipped if `language` is in input +// Initializer is skipped if language is in input data class Project(val name: String, val language: String = computeLanguage()) fun main() { @@ -135,7 +156,9 @@ fun main() { println(data) // Project(name=kotlinx.serialization, language=Java) } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -160,11 +183,16 @@ To ensure proper serialization, the referenced classes must also be annotated wi When encoded to JSON, this results in a nested JSON object: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable -// The 'owner' property references another serializable class `User` +// The owner property references another serializable class `User` class Project(val name: String, val owner: User) -// The referenced class must also be annotated with `@Serializable` +// The referenced class must also be annotated with @Serializable @Serializable class User(val name: String) @@ -174,7 +202,9 @@ fun main() { println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","owner":{"name":"kotlin"}} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -198,6 +228,11 @@ of arbitrary object graphs with repeated object references. For example, when serializing an object that references the same instance twice, it is simply encoded twice: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable class Project(val name: String, val owner: User, val maintainer: User) @@ -206,12 +241,14 @@ class User(val name: String) fun main() { val owner = User("kotlin") - // 'owner' is referenced twice + // owner is referenced twice val data = Project("kotlinx.serialization", owner, owner) println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -234,8 +271,13 @@ Generic classes in Kotlin provide type-polymorphic behavior, which is enforced b compile-time. For example, consider a generic serializable class `Box`: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable -// The `Box` class can be used with built-in types like `Int`, or with user-defined types like `Project`. +// The Box class can be used with built-in types like Int, or with user-defined types like Project. class Box(val contents: T) @Serializable data class Project(val name: String, val language: String) @@ -251,7 +293,9 @@ fun main() { println(Json.encodeToString(data)) // {"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -280,6 +324,11 @@ using the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotli For example, you can customize a property’s serial name to be shorter or more descriptive in the serialized output: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable // The language property is abbreviated to lang using @SerialName class Project(val name: String, @SerialName("lang") val language: String) @@ -290,7 +339,9 @@ fun main() { println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","lang":"Kotlin"} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -311,6 +362,11 @@ secondary constructor to handle the path string. Serialization works with a private primary constructor and still serializes only the backing fields: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable class Project private constructor(val owner: String, val name: String) { // Creates a Project object using a path string @@ -326,7 +382,9 @@ fun main() { println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) // {"owner":"kotlin","name":"kotlinx.serialization"} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -345,6 +403,11 @@ replace the parameter with a property in the primary constructor and perform val This ensures the class is serializable and that invalid data cannot be deserialized: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable class Project(val name: String) { // Validates that the name is not empty @@ -360,7 +423,9 @@ fun main() { println(data) // Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -381,6 +446,11 @@ To resolve this issue, you can add a default value to the property, which automa serialization: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable // Sets a default value for the optional `language` property data class Project(val name: String, val language: String = "Kotlin") @@ -392,7 +462,9 @@ fun main() { println(data) // Project(name=kotlinx.serialization, language=Kotlin) } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -411,10 +483,11 @@ This ensures that the property must be present in the input, even if it has a de ```kotlin // Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* import kotlinx.serialization.Required -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json +//sampleStart @Serializable // Marks the `language` property as required data class Project(val name: String, @Required val language: String = "Kotlin") @@ -426,7 +499,9 @@ fun main() { println(data) // MissingFieldException } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -453,6 +528,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.json.Json +//sampleStart @Serializable // Excludes the `language` property from serialization data class Project(val name: String, @Transient val language: String = "Kotlin") @@ -464,7 +540,9 @@ fun main() { println(data) // JsonDecodingException } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -489,6 +567,11 @@ This behavior improves efficiency by reducing visual clutter and minimizing the In the example below, the `language` property is omitted from the output because its value is equal to the default one: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable data class Project(val name: String, val language: String = "Kotlin") @@ -497,7 +580,9 @@ fun main() { println(Json.encodeToString(data)) // {"name":"kotlinx.serialization"} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -519,6 +604,11 @@ Let's look at an example, where the `language` property is included in the seria while the `projects` property is not serialized when it is an empty list: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable data class Project( val name: String, @@ -545,7 +635,9 @@ fun main() { println(Json.encodeToString(userB)) // {"name":"Bob"} } +//sampleEnd ``` +{kotlin-runnable="true"} diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md index b00190ba8f..1fc7eb1ac1 100644 --- a/docs/topics/serialization-get-started.md +++ b/docs/topics/serialization-get-started.md @@ -267,6 +267,7 @@ To deserialize an object from JSON in Kotlin: // Data(a=42, b="str") } ``` + {kotlin-runnable="true"} ## What's next? diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md index f7648f625e..33a56de119 100644 --- a/docs/topics/serialization-json-configuration.md +++ b/docs/topics/serialization-json-configuration.md @@ -66,7 +66,7 @@ It produces the following result: -## Customizing JSON parsing behavior +## Customize JSON parsing behavior Kotlin’s `Json` parser offers various settings to customize how JSON data is parsed and deserialized. @@ -150,7 +150,7 @@ Project(name=kotlinx.serialization) -## Managing default and null values +## Manage default and null values When working with JSON, managing default and `null` values helps ensure your data stays consistent. `kotlinx.serialization` provides control over when to include default values, how to handle nulls, diff --git a/docs/topics/serialization-polymorphism.md b/docs/topics/serialization-polymorphism.md index dd2d6e64d2..70a8d5089d 100644 --- a/docs/topics/serialization-polymorphism.md +++ b/docs/topics/serialization-polymorphism.md @@ -9,16 +9,14 @@ In the context of serialization, polymorphism helps manage data structures where ## Closed polymorphism in Kotlin serialization -Closed polymorphism ensures that all possible subclasses are known at compile-time, -making it a reliable and predictable approach for serializing and deserializing polymorphic data structures. +A class hierarchy is considered to use closed polymorphism when all possible subclasses are guaranteed to be known at compile-time. +This makes it a reliable and predictable approach for serializing and deserializing polymorphic data structures. The best way to serialize closed polymorphic classes is to use [`sealed classes`](#serialize-closed-polymorphic-classes), which restrict subclassing to the same file. Kotlin Serialization is fully static by default, where the structure of encoded objects is based on their compile-time types. This means that only the properties defined in the static type are serialized, even if the object is initialized with a subclass at runtime. -Let's look at an example where an `open class Project` has a `name` property, and its subclass `class OwnedProject` adds an `owner` property. -Although the `data` variable is initialized with an instance of `OwnedProject` at runtime, its compile-time type is `Project`. -As a result, only the properties of the `Project` class are serialized: +When serializing open classes, only the properties of the base class are serialized, and any additional properties in subclasses are ignored: ```kotlin // Imports the necessary libraries @@ -53,50 +51,10 @@ fun main() { -In this example, the `owner` property is not serialized because `data` is statically typed as `Project`. - -Changing the compile-time type of `data` to `OwnedProject` throws an exception: - -```kotlin -// Imports the necessary libraries -import kotlinx.serialization.* -import kotlinx.serialization.json.* - - -@Serializable -open class Project(val name: String) - -class OwnedProject(name: String, val owner: String) : Project(name) - -fun main() { -//sampleStart - val data = OwnedProject("kotlinx.coroutines", "kotlin") - // Throws an exception, because the `OwnedProject` class is not serializable - println(Json.encodeToString(data)) - // Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found. -} -//sampleEnd -``` -{kotlin-runnable="true" validate="false"} - -This exception occurs because the `OwnedProject` class is not serializable and cannot be annotated with `@Serializable` -as its primary constructor parameters (`name` and `owner`) are not properties. -To resolve this, you can [define constructor properties for serialization](serialization-customization-options.md#define-constructor-properties-for-serialization). - - - - - - - -Alternatively, you might consider defining `Project` as an [`abstract class`](classes.md#abstract-classes) with abstract properties. -That way, instead of `Project` providing actual values for its properties, its subclasses are responsible for defining them. -Unfortunately, this still throws an exception: +Alternatively, you might consider defining the base class as an [`abstract class`](classes.md#abstract-classes) with abstract properties, +allowing subclasses to provide actual values for those properties. +However, this approach still doesn't resolve the issue with serialization, +and attempting to serialize instances of subclasses will result in an exception: ```kotlin // Imports the necessary libraries @@ -120,9 +78,6 @@ fun main() { ``` {kotlin-runnable="true"} -This exception indicates that defining `Project` as an abstract class doesn't resolve the serialization issue. -To fix this, you need to [use sealed classes](#serialize-closed-polymorphic-classes). - +This exception highlights that using an `abstract class` still doesn't solve the issue. +To fix this and ensure proper serialization of subclasses, you need to [use sealed classes](#serialize-closed-polymorphic-classes), +which provide the necessary compile-time guarantees for closed polymorphic serialization. + +> To handle polymorphism with `open` and `abstract` classes, you must use [open polymorphism with explicit configuration](#open-polymorphism-in-kotlin-serialization). +> +{type="note"} + ### Serialize closed polymorphic classes -The simplest way to handle polymorphic serialization is to use a `sealed class` as the base. -_All_ subclasses of a sealed class must be explicitly marked as `@Serializable`. +You can serialize closed polymorphic classes by using a `sealed class` as the base. +All subclasses of a sealed class must be explicitly marked as `@Serializable`. This approach ensures that polymorphism is represented in JSON with a type discriminator: ```kotlin @@ -265,7 +228,6 @@ You can change this by using the [`@SerialName`](https://kotlinlang.org/api/kotl corresponding class. This allows you to define stable _serial name_ that remains consistent, regardless of changes to the class name in the source code: - ```kotlin // Imports the necessary libraries import kotlinx.serialization.* @@ -406,8 +368,10 @@ As a result, the list of subclasses cannot be determined at compile time and mus To serialize and deserialize instances of open polymorphic classes, you need to provide all subclasses in a `SerializersModule` class. You can use the [`SerializersModule {}`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html) builder function to create an instance of the `SerializersModule`. -Within this module, specify the base class in the [`polymorphic()`] builder function and specify each subclass with the [`subclass()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html) function. -This module is then passed to the `Json` configuration: +Within this module, specify the base class in the [`polymorphic()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/polymorphic.html) builder function. +Then, _register_ each subclass with the [`subclass()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html) function. +Registering means specifying the subclass so the serialization framework can recognize and handle these types. +Once the subclasses are registered, the module is then passed to the `Json` configuration: ```kotlin // Imports the necessary libraries @@ -419,7 +383,7 @@ import kotlinx.serialization.modules.* // Defines a SerializersModule with polymorphic serialization val module = SerializersModule { polymorphic(Project::class) { - // Specifies OwnedProject as a subclass of Project + // Registers OwnedProject as a subclass of Project subclass(OwnedProject::class) } } @@ -474,7 +438,7 @@ but allows the serialization of open polymorphic classes. ### Serialize interfaces Although you cannot annotate an interface with `@Serializable`, interfaces are implicitly serializable with `PolymorphicSerializer`. -To do so, mark the classes that implement the interface as `@Serializable` and specify them in a `SerializersModule`: +To do so, mark the classes that implement the interface as `@Serializable` and register them in a `SerializersModule`: ```kotlin // Imports the necessary libraries @@ -483,7 +447,7 @@ import kotlinx.serialization.json.* import kotlinx.serialization.modules.* //sampleStart -// Creates a SerializersModule to specify the implementing classes of the interface +// Creates a SerializersModule to register the implementing classes of the interface val module = SerializersModule { polymorphic(Project::class) { subclass(OwnedProject::class) @@ -510,6 +474,7 @@ fun main() { } //sampleEnd ``` +{kotlin-runnable="true"} @@ -562,6 +527,7 @@ fun main() { } //sampleEnd ``` +{kotlin-runnable="true"} @@ -573,106 +539,26 @@ fun main() { -### Static parent type lookup for polymorphism +### Serialize polymorphic types with generic base types -During serialization of a polymorphic class the root type of the polymorphic hierarchy (`Project` in our example) -is determined statically. Let us take the example with the serializable `abstract class Project`, -but change the `main` function to declare `data` as having a type of `Any`: +Since Kotlin Serialization is fully static, the root type of a polymorphic hierarchy is determined at compile time. +When the base type is generic, such as `Any`, the system cannot resolve the correct subclass without additional instructions. - - -```kotlin -fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-12.kt). - -We get the exception. - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. -Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. -``` - - - -We have to register classes for polymorphic serialization with respect for the corresponding static type we -use in the source code. First of all, we change our module to register a subclass of `Any`: - - +1. Specify the generic base type and its subclasses in a `SerializersModule`. +2. Use `PolymorphicSerializer` as the first parameter to the [`encodeToString()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html) function. +This explicitly informs the system how to handle the polymorphic structure at runtime. ```kotlin -val module = SerializersModule { - polymorphic(Any::class) { - subclass(OwnedProject::class) - } -} -``` - - - -Then we can try to serialize the variable of type `Any`: - -```kotlin -fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-13.kt). - -However, `Any` is a class and it is not serializable: - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. -Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. -``` - - - -We must to explicitly pass an instance of [PolymorphicSerializer] for the base class `Any` as the -first parameter to the [encodeToString][Json.encodeToString] function. - - -```kotlin fun main() { + // Declares data as Any, requiring explicit handling of polymorphism val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") + // Uses PolymorphicSerializer to serialize data of type Any println(format.encodeToString(PolymorphicSerializer(Any::class), data)) -} + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} +//sampleEnd ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-poly-14.kt). - -With the explicit serializer it works as before. + + -### Explicitly marking polymorphic class properties +If the subclass is not registered or the `PolymorphicSerializer` is not provided, an exception is thrown: -The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism. -However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type. -If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization -strategy via the [`@Serializable`][Serializable] annotation as we saw in -the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section. -To specify a polymorphic serialization strategy of a property, the special-purpose [`@Polymorphic`][Polymorphic] -annotation is used. +```text +Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. +Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. +``` + +### Serialize non-serializable properties polymorphically - -```kotlin @Serializable class Data( - @Polymorphic // the code does not compile without it + // Ensures the project property is serialized using PolymorphicSerializer + @Polymorphic val project: Any ) fun main() { val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) println(format.encodeToString(data)) + // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} } +//sampleEnd ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-poly-15.kt). + -### Registering multiple superclasses +### Register multiple superclasses + +When the same class is serialized as a value of properties with different compile-time types from its list of +superclasses, you must register it in the [SerializersModule](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/) for each of its superclasses separately. +To do so, you can use the [`subclass()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html) function. -When the same class gets serialized as a value of properties with different compile-time type from the list of -its superclasses, we must register it in the [SerializersModule] for each of its superclasses separately. -It is convenient to extract registration of all the subclasses into a separate function and -use it for each superclass. You can use the following template to write it. +To avoid repeating `subclass()` calls for each superclass, you can create a function that registers the subclasses and apply it to each superclass. +For example: - -```kotlin +//sampleStart val module = SerializersModule { + // Creates a function to register subclasses for each superclass fun PolymorphicModuleBuilder.registerProjectSubclasses() { subclass(OwnedProject::class) } + // Applies the subclass registration to Any and Project polymorphic(Any::class) { registerProjectSubclasses() } polymorphic(Project::class) { registerProjectSubclasses() } -} -``` - - + // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"},"any":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} +} +``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-poly-16.kt). + -### Polymorphism and generic classes +### Serialize generic subtypes in a polymorphic hierarchy -Generic subtypes for a serializable class require a special handling. Consider the following hierarchy. +When serializing generic subtypes in a polymorphic class hierarchy, Kotlin Serialization requires explicit handling. +Since the framework cannot automatically determine the actual type of generic parameters, +you can use a `PolymorphicSerializer` to handle these cases. - +To serialize generic subtypes, register the appropriate serializers within a `SerializersModule`, ensuring that +each polymorphic type is correctly serialized and deserialized within the hierarchy: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* + +// Defines an abstract response class with a generic parameter T @Serializable abstract class Response - + +// Represents a successful response with a generic data type @Serializable @SerialName("OkResponse") data class OkResponse(val data: T) : Response() -``` -Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the -type parameter `T` when serializing a property of the polymorphic type `OkResponse`. We have to provide this -strategy explicitly when defining the serializers module for `Response`. In the below example we -use `OkResponse.serializer(...)` to retrieve -the [Plugin-generated generic serializer](serializers.md#plugin-generated-generic-serializer) of -the `OkResponse` class and instantiate it with the [PolymorphicSerializer] instance with -`Any` class as its base. This way, we can serialize an instance of `OkResponse` with any `data` property that -was polymorphically registered as a subtype of `Any`. +// Defines the abstract class Project +@Serializable +abstract class Project { + abstract val name: String +} -```kotlin +// Concrete subclass of Project +@Serializable +@SerialName("OwnedProject") +data class OwnedProject(override val name: String, val owner: String) : Project() + +//sampleStart +// Defines a serializers module for polymorphic classes val responseModule = SerializersModule { polymorphic(Response::class) { + // Registers the polymorphic serializer for OkResponse subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) } + polymorphic(Any::class) { + // Registers OwnedProject as a subclass of Any + subclass(OwnedProject::class) + } + polymorphic(Project::class) { + // Registers OwnedProject as a subclass of Project + subclass(OwnedProject::class) + } +} + +// Creates a Json format with the registered serializers +val format = Json { serializersModule = responseModule } + +fun main() { + // Creates an instance of OkResponse with a Project subtype + val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) + + // Serializes the data to JSON + val jsonString = format.encodeToString(data) + println(jsonString) + // {"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} + + // Deserializes the JSON back to Response + val deserializedData = format.decodeFromString>(jsonString) + println(deserializedData) + // OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) } ``` +{kotlin-runnable="true"} -### Merging library serializers modules + -When the application grows in size and splits into source code modules, -it may become inconvenient to store all class hierarchies in one serializers module. -Let us add a library with the `Project` hierarchy to the code from the previous section. + + + + +### Merge multiple SerializerModule instances + +As an application grows and splits into multiple source code modules, managing all class hierarchies within a single `SerializerModule` may become cumbersome. +You can combine multiple `SerializersModule` instances using the [`plus`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/plus.html) operator, +allowing them to be used together in the same `Json` format instance: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* + +//sampleStart +@Serializable +abstract class Response + +@Serializable +@SerialName("OkResponse") +data class OkResponse(val data: T) : Response() + +val responseModule = SerializersModule { + polymorphic(Response::class) { + subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) + } +} + val projectModule = SerializersModule { fun PolymorphicModuleBuilder.registerProjectSubclasses() { subclass(OwnedProject::class) @@ -857,181 +829,144 @@ val projectModule = SerializersModule { polymorphic(Any::class) { registerProjectSubclasses() } polymorphic(Project::class) { registerProjectSubclasses() } } -``` - - -We can compose those two modules together using the [plus] operator to merge them, -so that we can use them both in the same [Json] format instance. - -> You can also use the [include][SerializersModuleBuilder.include] function -> in the [SerializersModule {}][SerializersModule()] DSL. - -```kotlin +// Now classes from both hierarchies can be serialized together and deserialized together. val format = Json { serializersModule = projectModule + responseModule } -```` - -Now classes from both hierarchies can be serialized together and deserialized together. +// The JSON that is being produced is deeply polymorphic. +//sampleEnd -```kotlin fun main() { // both Response and Project are abstract and their concrete subtypes are being serialized val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) val string = format.encodeToString(data) println(string) + // {"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} println(format.decodeFromString>(string)) + // OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) } - ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-poly-17.kt). +> You can also use the [`include()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/include.html) function +> within the [`SerializersModule`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html) block: +> +> ```kotlin +> // Combines the two modules using the `include()` function +> val combinedModule = SerializersModule { +> include(projectModule) +> include(responseModule) +> } +> ``` +> +{type="note"} + +If you're building a library or shared module that defines an abstract class and its implementations, +you can expose your `SerializersModule` to your clients. +This allows them to combine your module with their own `SerializersModule` for seamless integration. -The JSON that is being produced is deeply polymorphic. + + -If you're writing a library or shared module with an abstract class and some implementations of it, -you can expose your own serializers module for your clients to use so that a client can combine your -module with their modules. - ### Default polymorphic type handler for deserialization -What happens when we deserialize a subclass that was not registered? - - +In the following example, the `BasicProject` class is used to represent unknown subtypes. +The example doesn't rely on the `type` field to differentiate subtypes and instead uses the [plugin-generated serializer]((serializers.md#plugin-generated-serializer)) for `BasicProject`: ```kotlin -fun main() { - println(format.decodeFromString(""" - {"type":"unknown","name":"example"} - """)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-18.kt). - -We get the following exception. - -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $ -Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule. -``` - - - -When reading a flexible input we might want to provide some default behavior in this case. For example, -we can have a `BasicProject` subtype to represent all kinds of unknown `Project` subtypes. - - -```kotlin +//sampleStart @Serializable abstract class Project { abstract val name: String } +// Represents unknown project types, capturing the type and name @Serializable data class BasicProject(override val name: String, val type: String): Project() @Serializable @SerialName("OwnedProject") data class OwnedProject(override val name: String, val owner: String) : Project() -``` - -We register a default deserializer handler using the [`defaultDeserializer`][PolymorphicModuleBuilder.defaultDeserializer] function in -the [`polymorphic { ... }`][PolymorphicModuleBuilder] DSL that defines a strategy which maps the `type` string from the input -to the [deserialization strategy][DeserializationStrategy]. In the below example we don't use the type, -but always return the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) -of the `BasicProject` class. -```kotlin +// Registers a default deserializer for unknown Project subtypes val module = SerializersModule { polymorphic(Project::class) { subclass(OwnedProject::class) defaultDeserializer { BasicProject.serializer() } } } -``` - -Using this module we can now deserialize both instances of the registered `OwnedProject` and -any unregistered one. -```kotlin val format = Json { serializersModule = module } fun main() { + // Deserializes both a known and an unknown Project subtype println(format.decodeFromString>(""" [ {"type":"unknown","name":"example"}, {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} ] """)) + // [BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)] } +//sampleEnd ``` -> You can get the full code [here](../../guide/example/example-poly-19.kt). +Using a default serializer assumes that the structure of the "unknown" data is known in advance. +In cases where the structure may vary, you need to create a custom serializer to handle more flexible or less-structured data. +For more details on working with custom JSON structures, see the [Maintaining custom JSON attributes]((serialization-json-transform-json.md#maintain-custom-json-attributes)) section. -Notice, how `BasicProject` had also captured the specified type key in its `type` property. + + -We used a plugin-generated serializer as a default serializer, implying that -the structure of the "unknown" data is known in advance. In a real-world API it's rarely the case. -For that purpose a custom, less-structured serializer is needed. You will see the example of such serializer in the future section -on [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attributes). - ### Default polymorphic type handler for serialization -Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you -don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can register a default serializer. +To dynamically choose a serializer for a polymorphic type based on the instance, use the [`polymorphicDefaultSerializer()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/polymorphic-default-serializer.html) function within the [`SerializersModule { ... }`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/) DSL. +This function defines a strategy that takes an instance of the base class and provides a corresponding [`SerializationStrategy`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/). - -```kotlin +// sampleStart interface Animal { } @@ -1051,18 +986,13 @@ private class DogImpl : Dog { override val dogType: String = "Husky" } +// Provides instances of Cat and Dog object AnimalProvider { fun createCat(): Cat = CatImpl() fun createDog(): Dog = DogImpl() } -``` - -We register a default serializer handler using the [`polymorphicDefaultSerializer`][SerializersModuleBuilder.polymorphicDefaultSerializer] function in -the [`SerializersModule { ... }`][SerializersModuleBuilder] DSL that defines a strategy which takes an instance of the base class and -provides a [serialization strategy][SerializationStrategy]. In the below example we use a `when` block to check the type of the -instance, without ever having to refer to the private implementation classes. -```kotlin +// Registers a default serializer for unknown Animal subtypes val module = SerializersModule { polymorphicDefaultSerializer(Animal::class) { instance -> @Suppress("UNCHECKED_CAST") @@ -1074,46 +1004,48 @@ val module = SerializersModule { } } +// Defines custom serializers for Cat and Dog object CatSerializer : SerializationStrategy { override val descriptor = buildClassSerialDescriptor("Cat") { element("catType") } - + override fun serialize(encoder: Encoder, value: Cat) { encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.catType) + encodeStringElement(descriptor, 0, value.catType) } } } object DogSerializer : SerializationStrategy { - override val descriptor = buildClassSerialDescriptor("Dog") { - element("dogType") - } + override val descriptor = buildClassSerialDescriptor("Dog") { + element("dogType") + } - override fun serialize(encoder: Encoder, value: Dog) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.dogType) + override fun serialize(encoder: Encoder, value: Dog) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.dogType) + } } - } } -``` - -Using this module we can now serialize instances of `Cat` and `Dog`. - -```kotlin +// Creates a Json format using the registered module val format = Json { serializersModule = module } fun main() { + // Serializes an instance of Cat println(format.encodeToString(AnimalProvider.createCat())) + // {"type":"Cat","catType":"Tabby"} } +//sampleEnd ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-poly-20.kt) + + From 380b282718c9a08943f2a50df2cef2a80fa65862 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Fri, 27 Sep 2024 16:36:57 +0200 Subject: [PATCH 08/14] continuing work with custom serializers --- docs/serialization.tree | 2 +- docs/topics/create-custom-serializers.md | 180 ++++++++++++--------- docs/topics/serialization-json-elements.md | 2 +- 3 files changed, 103 insertions(+), 81 deletions(-) diff --git a/docs/serialization.tree b/docs/serialization.tree index 286e9e7c04..4b03241f97 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -16,7 +16,7 @@ - + diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index f76e3e7938..7b103c1037 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -34,12 +34,15 @@ Color(rgb: kotlin.Int) -However, if you need to customize how your data is encoded and decoded, such as converting an RGB value into a hexadecimal string and back, -you can create a custom serializer. +## Create a custom primitive serializer + +If you need more control over how your data is encoded and decoded, you can create a custom serializer. +This allows you to customize how your data is represented, such as converting an RGB integer value into a hexadecimal string. + Custom serializers implement the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) interface and allowing you to control how your data is transformed between its Kotlin object form and its serialized form. -To create a custom serializer: +To create a custom primitive serializer: 1. Use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation with the [`with`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/with.html) property to bind a custom serializer to a class. The `with` property specifies the serializer used for the bound class. 2. Create an `object` that implements the `KSerializer` interface. This allows you to define how instances of your class are serialized and deserialized. @@ -183,67 +186,73 @@ For more information on how format can treat arrays, see the [Format specific ty -### Composite serializer via surrogate +### Serialize with a surrogate class + +To serialize a class in a specific structure, you can create a _surrogate class_. +A surrogate class is a separate class that mirrors the desired serialized form of your original class, allowing you to customize how the data is structured during serialization. +This allows you to enforce specific constraints, like ensuring that property values remain within a valid range. +The surrogate class can also have a [custom `@SerialName`](serialization-customization-options.md#customize-serial-names) annotation, +making it indistinguishable from the original class when formats rely on class names. + +Like with [delegated serialization]((#delegate-serialization-to-another-serializer)), surrogate classes also rely on reusing existing serialization logic. +However, instead of transforming or restructuring individual fields within the same class, +you create a new surrogate class to represent the serialized form. -Now our challenge is to get `Color` serialized so that it is represented in JSON as if it is a class -with three properties—`r`, `g`, and `b`—so that JSON encodes it as an object. -The easiest way to achieve this is to define a _surrogate_ class mimicking the serialized form of `Color` that -we are going to use for its serialization. We also set the [SerialName] of this surrogate class to `Color`. Then if -any format uses this name the surrogate looks like it is a `Color` class. -The surrogate class can be `private`, and can enforce all the constraints on the serial representation -of the class in its `init` block. +The surrogate class can be [`private`](visibility-modifiers.md#class-members), and you use the `init` block to enforce constraints on the serialized data. +After defining the surrogate class, you can retrieve the plugin-generated serializer for it by calling its `serializer()` function. +The function reuses the automatically generated [`SerialDescriptor`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/) of +the surrogate class to describe the structure of the serialized data while maintaining compatibility with the original class. + +To serialize with the surrogate class, delegate the serialization process to its serializer by using the `encodeSerializableValue()` and `decodeSerializableValue()` functions: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.builtins.IntArraySerializer +import kotlinx.serialization.json.* + +//sampleStart +// Defines a private surrogate class with custom properties @Serializable @SerialName("Color") private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { - init { + init { + // Ensures values are within a valid range require(r in 0..255 && g in 0..255 && b in 0..255) } } -``` - -> An example of where the class name is used is shown in -> the [Custom subclass serial name](polymorphism.md#custom-subclass-serial-name) section in the chapter on polymorphism. - -Now we can use the `ColorSurrogate.serializer()` function to retrieve a plugin-generated serializer for the -surrogate class. -We can use the same approach as in [delegating serializer](#delegating-serializers), but this time, -we are fully reusing an automatically -generated [SerialDescriptor] for the surrogate because it should be indistinguishable from the original. - -```kotlin +// Custom serializer that delegates to the surrogate class object ColorSerializer : KSerializer { override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor + // Serializes the original class as a surrogate override fun serialize(encoder: Encoder, value: Color) { val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) } + // Deserializes the surrogate back into the original class override fun deserialize(decoder: Decoder): Color { val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) } } -``` - -We bind the `ColorSerializer` serializer to the `Color` class. -```kotlin +// Binds the ColorSerializer serializer to the original class @Serializable(with = ColorSerializer::class) class Color(val rgb: Int) -``` - -Now we can enjoy the result of serialization for the `Color` class. +//sampleEnd - +``` +{kotlin-runnable="true"} @@ -255,60 +264,75 @@ fun main() { -### Hand-written composite serializer +## Create a custom composite serializer -There are some cases where a surrogate solution does not fit. Perhaps we want to avoid the performance -implications of additional allocation, or we want a configurable/dynamic set of properties for the -resulting serial representation. In these cases we need to manually write a class -serializer which mimics the behaviour of a generated serializer. +Unlike custom primitive serializers, which handle a single value like a string or integer, composite serializers +can represent complex data structures, such as classes with multiple properties. -```kotlin -object ColorAsObjectSerializer : KSerializer { -``` +To create a custom composite serializer: + +1. Define the serialization schema by overriding the `descriptor` property. Use the [`buildClassSerialDescriptor()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/build-class-serial-descriptor.html) function to define the class’s schema. +The descriptor outlines the structure of the serialized data, specifying its fields and their order. +2. Specify the elements of the class in the `descriptor` by calling the [`element()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/element.html) function for each property. +The order of the elements is important and must match the order in which they are serialized and deserialized. + + > The term "element" refers to different parts of the serialized data depending on its [`SerialKind`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-kind/). + > For class descriptors, elements represent properties, while for enum descriptors, elements represent constants such as RED or GREEN in an enum class. + > + {type="note"} -Let's introduce it piece by piece. First, a descriptor is defined using the [buildClassSerialDescriptor] builder. -The [element][ClassSerialDescriptorBuilder.element] function in the builder DSL automatically fetches serializers -for the corresponding fields by their type. The order of elements is important. They are indexed starting from zero. +3. Implement the `serialize()` function using the [`encodeStructure()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html) DSL. +Inside the block, call the appropriate [`CompositeEncoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/) functions like [`encodeIntElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-int-element.html) for each field, following the order defined in the descriptor. +4. Implement the `deserialize()` function using the [`decodeStructure()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/decode-structure.html) DSL. +Within this block, use the `CompositeDecoder` to decode each property by calling functions like [`decodeIntElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-int-element.html). +You can use [`decodeElementIndex()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html) to identify which element to decode. The decoding order may vary depending on the format, such as JSON. +5. Bind the custom serializer to the class by specifying it with the `@Serializable(with = YourSerializer::class)` annotation. +This ensures that the class uses the custom composite serializer during serialization and deserialization. + + > If you only need to transform or restructure data without manually handling each element, + > consider using [delegated serialization](#delegate-serialization-to-another-serializer) or [surrogate serialization](#serialize-with-a-surrogate-class) for a simpler approach. + > + {type="tip"} + +Let's look at an example of how to serialize a class with multiple properties: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* + +//sampleStart +// Creates a custom serializer for the Color class with multiple properties +object ColorAsObjectSerializer : KSerializer { + // Defines the schema for the Color class + // specifying the properties with the buildClassSerialDescriptor() function override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Color") { + // Specifies each property with its type and name with the element() function element("r") element("g") element("b") } -``` - -> The "element" is a generic term here. What is an element of a descriptor depends on its [SerialKind]. -> Elements of a class descriptor are its properties, elements of a enum descriptor are its cases, etc. - -Then we write the `serialize` function using the [encodeStructure] DSL that provides access to -the [CompositeEncoder] in its block. The difference between [Encoder] and [CompositeEncoder] is the latter -has `encodeXxxElement` functions that correspond to the `encodeXxx` functions of the former. They must be called -in the same order as in the descriptor. -```kotlin + // Serializes the Color object into a structured format override fun serialize(encoder: Encoder, value: Color) = encoder.encodeStructure(descriptor) { + // Encodes the red, green, and blue values in the specified order encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) encodeIntElement(descriptor, 2, value.rgb and 0xff) } -``` - -The most complex piece of code is the `deserialize` function. It must support formats, like JSON, that -can decode properties in an arbitrary order. It starts with the call to [decodeStructure] to -get access to a [CompositeDecoder]. Inside it we write a loop that repeatedly calls -[decodeElementIndex][CompositeDecoder.decodeElementIndex] to decode the index of the next element, then we decode the corresponding -element using [decodeIntElement][CompositeDecoder.decodeIntElement] in our example, and finally we terminate the loop when -`CompositeDecoder.DECODE_DONE` is encountered. -```kotlin + // Deserializes the data back into a Color object override fun deserialize(decoder: Decoder): Color = decoder.decodeStructure(descriptor) { + // Temporary variables to hold the decoded values var r = -1 var g = -1 var b = -1 + // Loops to decode each property by its index while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> r = decodeIntElement(descriptor, 0) @@ -318,38 +342,36 @@ element using [decodeIntElement][CompositeDecoder.decodeIntElement] in our examp else -> error("Unexpected index: $index") } } + // Ensures the values are valid and returns a new Color object require(r in 0..255 && g in 0..255 && b in 0..255) Color((r shl 16) or (g shl 8) or b) } -``` - - - -Now we bind the resulting serializer to the `Color` class and test its serialization/deserialization. -```kotlin +// Binds the custom serializer to the Color class @Serializable(with = ColorAsObjectSerializer::class) data class Color(val rgb: Int) +//sampleEnd fun main() { val color = Color(0x00ff00) - val string = Json.encodeToString(color) + val string = Json.encodeToString(color) println(string) + // {"r":0,"g":255,"b":0} require(Json.decodeFromString(string) == color) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-12.kt). +} +``` +{kotlin-runnable="true"} -As before, we got the `Color` class represented as a JSON object with three keys: + + - + ### Sequential decoding protocol (experimental) @@ -421,7 +443,7 @@ fun main() { {"r":0,"g":255,"b":0} --> -### Serializing 3rd party classes +## Serialize 3rd party classes Sometimes an application has to work with an external type that is not serializable. Let us use [java.util.Date] as an example. As before, we start by writing an implementation of [KSerializer] diff --git a/docs/topics/serialization-json-elements.md b/docs/topics/serialization-json-elements.md index d3047123d9..b3732669e9 100644 --- a/docs/topics/serialization-json-elements.md +++ b/docs/topics/serialization-json-elements.md @@ -1,5 +1,5 @@ -[//]: # (title: Managing JSON elements) +[//]: # (title: Manage JSON elements) Kotlin serialization provides APIs that allow more than just direct conversions between strings and JSON objects. For example, you might want to modify data before it can be parsed or work with unstructured data that doesn't fit neatly into the type-safe world of Kotlin serialization. From 1f9c49e74e78bff277e171030b80e50a7da6b152 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Tue, 1 Oct 2024 09:08:30 +0200 Subject: [PATCH 09/14] created new document third party classes --- docs/serialization.tree | 5 +- docs/topics/create-custom-serializers.md | 283 +++-------------------- docs/topics/third-party-classes.md | 266 +++++++++++++++++++++ 3 files changed, 302 insertions(+), 252 deletions(-) create mode 100644 docs/topics/third-party-classes.md diff --git a/docs/serialization.tree b/docs/serialization.tree index 4b03241f97..39c7217aca 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -20,7 +20,10 @@ - + + + + \ No newline at end of file diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index 7b103c1037..f6a7ffc63c 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -113,6 +113,7 @@ fun main() { + ## Delegate serialization to another serializer To serialize a class as a different type, you can delegate the serialization logic to another serializer. @@ -373,15 +374,22 @@ fun main() { -### Sequential decoding protocol (experimental) +### Optimize deserialization with sequential decoding (experimental) + +For formats that store complex data in sequential order, like JSON, you can create a more optimized deserialization process by calling the [`decodeSequentially()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html) function. +This function checks whether the data is stored sequentially, allowing you to skip the more complex logic of decoding individual elements out of order. + +If `decodeSequentially()` returns true, you can directly decode the elements in order, eliminating the need for `decodeElementIndex()` in a loop to determine the order of elements. +If it returns `false`, you revert to the standard deserialization method, which handles unordered formats: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* -The implementation of the `deserialize` function from the previous section works with any format. However, -some formats either always store all the complex data in order, or only do so sometimes (JSON always stores -collections in order). With these formats the complex protocol of calling `decodeElementIndex` in the loop is -not needed, and a faster implementation can be used if the [CompositeDecoder.decodeSequentially] function returns `true`. -The plugin-generated serializers are actually conceptually similar to the below code. - -```kotlin +//sampleStart override fun deserialize(decoder: Decoder): Color = decoder.decodeStructure(descriptor) { var r = -1 var g = -1 - var b = -1 - if (decodeSequentially()) { // sequential decoding protocol + var b = -1 + // Decodes values directly in order if the format stores data sequentially + if (decodeSequentially()) { r = decodeIntElement(descriptor, 0) g = decodeIntElement(descriptor, 1) b = decodeIntElement(descriptor, 2) } else while (true) { + // Decodes elements using a loop to handle formats where data may be unordered when (val index = decodeElementIndex(descriptor)) { 0 -> r = decodeIntElement(descriptor, 0) 1 -> g = decodeIntElement(descriptor, 1) @@ -421,257 +430,29 @@ object ColorAsObjectSerializer : KSerializer { require(r in 0..255 && g in 0..255 && b in 0..255) Color((r shl 16) or (g shl 8) or b) } -``` - - - -> You can get the full code [here](../../guide/example/example-serializer-13.kt). - - - -## Serialize 3rd party classes - -Sometimes an application has to work with an external type that is not serializable. -Let us use [java.util.Date] as an example. As before, we start by writing an implementation of [KSerializer] -for the class. Our goal is to get a `Date` serialized as a long number of milliseconds following the -approach from the [Primitive serializer](#primitive-serializer) section. - -> In the following sections any kind of `Date` serializer would work. For example, if we want `Date` to be serialized -> as an object, we would use an approach from -> the [Composite serializer via surrogate](#composite-serializer-via-surrogate) section. -> See also [Deriving external serializer for another Kotlin class (experimental)](#deriving-external-serializer-for-another-kotlin-class-experimental) -> when you need to serialize a 3rd-party Kotlin class that could have been serializable, but is not. - - - -```kotlin -object DateAsLongSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) - override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) - override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) -} -``` - -We cannot bind the `DateAsLongSerializer` serializer to the `Date` class with the [`@Serializable`][Serializable] annotation -because we don't control the `Date` source code. There are several ways to work around that. - -### Passing a serializer manually - -All `encodeToXxx` and `decodeFromXxx` functions have an overload with the first serializer parameter. -When a non-serializable class, like `Date`, is the top-level class being serialized, we can use those. - -```kotlin -fun main() { - val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00") - println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-14.kt). - -```text -1455494400000 -``` - - - -### Specifying serializer on a property - -When a property of a non-serializable class, like `Date`, is serialized as part of a serializable class we must supply -its serializer or the code will not compile. This is accomplished using the [`@Serializable`][Serializable] annotation on the property. - - - -```kotlin -@Serializable -class ProgrammingLanguage( - val name: String, - @Serializable(with = DateAsLongSerializer::class) - val stableReleaseDate: Date -) - -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-15.kt). - -The `stableReleaseDate` property is serialized with the serialization strategy that we specified for it: - -```text -{"name":"Kotlin","stableReleaseDate":1455494400000} -``` - - - -### Specifying serializer for a particular type - -[`@Serializable`][Serializable] annotation can also be applied directly to the types. -This is handy when a class that requires a custom serializer, such as `Date`, happens to be a generic type argument. -The most common use case for that is when you have a list of dates: - - - -```kotlin -@Serializable -class ProgrammingLanguage( - val name: String, - val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> -) - -fun main() { - val df = SimpleDateFormat("yyyy-MM-ddX") - val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) - println(Json.encodeToString(data)) } ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-serializer-16.kt). - -```text -{"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]} -``` - - - -### Specifying serializers for a file - -A serializer for a specific type, like `Date`, can be specified for a whole source code file with the file-level -[UseSerializers] annotation at the beginning of the file. - -```kotlin -@file:UseSerializers(DateAsLongSerializer::class) -``` - - - - - -Now a `Date` property can be used in a serializable class without additional annotations. - -```kotlin -@Serializable -class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) - -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(Json.encodeToString(data)) -} -``` -> You can get the full code [here](../../guide/example/example-serializer-17.kt). - -```text -{"name":"Kotlin","stableReleaseDate":1455494400000} -``` - - - -### Specifying serializer globally using typealias - -kotlinx.serialization tends to be the always-explicit framework when it comes to serialization strategies: normally, -they should be explicitly mentioned in `@Serializable` annotation. Therefore, we do not provide any kind of global serializer -configuration (except for [context serializer](#contextual-serialization) mentioned later). - -However, in projects with a large number of files and classes, it may be too cumbersome to specify `@file:UseSerializers` -every time, especially for classes like `Date` or `Instant` that have a fixed strategy of serialization across the project. -For such cases, it is possible to specify serializers using `typealias`es, as they preserve annotations, including serialization-related ones: - -object DateAsSimpleTextSerializer: KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG) - private val format = SimpleDateFormat("yyyy-MM-dd").apply { - // Here we explicitly set time zone to UTC so output for this sample remains locale-independent. - // Depending on your needs, you may have to adjust or remove this line. - setTimeZone(TimeZone.getTimeZone("UTC")) - } - override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value)) - override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString()) -} + -```kotlin -typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date - -typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date -``` - -Using these new different types, it is possible to serialize a Date differently without additional annotations: - -```kotlin -@Serializable -class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) - -fun main() { - val format = SimpleDateFormat("yyyy-MM-ddX") - val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-18.kt). - -```text -{"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000} -``` - - - -### Custom serializers for a generic type +## Create a custom serializer for a generic type Let us take a look at the following example of the generic `Box` class. It is marked with `@Serializable(with = BoxSerializer::class)` as we plan to have a custom serialization @@ -680,7 +461,7 @@ strategy for it. ```kotlin @Serializable(with = BoxSerializer::class) data class Box(val contents: T) -``` +``` An implementation of [KSerializer] for a regular type is written as an `object`, as we saw in this chapter's examples for the `Color` type. A generic class serializer is instantiated with serializers @@ -718,11 +499,11 @@ The resulting JSON looks like the `Project` class was serialized directly. ```text {"name":"kotlinx.serialization"} Box(contents=Project(name=kotlinx.serialization)) -``` +``` -### Format-specific serializers +## Format-specific serializers The above custom serializers worked in the same way for every format. However, there might be format-specific features that a serializer implementation would like to take advantage of. diff --git a/docs/topics/third-party-classes.md b/docs/topics/third-party-classes.md new file mode 100644 index 0000000000..e6e70fc984 --- /dev/null +++ b/docs/topics/third-party-classes.md @@ -0,0 +1,266 @@ +[//]: # (title: Serialize third-party classes) + + + +Third-party types, such as [java.util.Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html), cannot be directly annotated with [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) because you do not have control over their source code. +To serialize these non-serializable classes, you can implement a custom [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/), as described in the [Create a custom primitive serializer](create-custom-serializers.md#create-a-custom-primitive-serializer) section. + +In this section, you can explore several approaches to working around this limitation, using `java.util.Date` as an example. +The goal is to serialize a `Date` object as a long value representing the number of milliseconds since the Unix epoch. + +## Pass serializers manually + +To serialize a non-serializable class like `Date`, you can manually pass a custom serializer using the overloads of +`Encoder` and `Decoder` functions, such as [`encodeLong()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-long.html) or [`decodeLong()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-long.html): + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* +import java.util.Date +import java.text.SimpleDateFormat + +//sampleStart +// Cannot use @Serializable on Date as without control over its source code +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} + +fun main() { + val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00") + // Serializes Date as a Long in milliseconds + println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate)) + // 1455494400000 +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +## Specify serializer on a property + +When a non-serializable class, like `Date`, is used as a property in a serializable class, you must specify its serializer to ensure the code compiles. +To do so, use the `@Serializable` annotation on the property: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* +import java.util.Date +import java.text.SimpleDateFormat + +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} + +//sampleStart +@Serializable +class ProgrammingLanguage( + val name: String, + // Specifies the custom serializer for the Date property + @Serializable(with = DateAsLongSerializer::class) + val stableReleaseDate: Date +) + +fun main() { + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(Json.encodeToString(data)) + // {"name":"Kotlin","stableReleaseDate":1455494400000} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +## Specify a custom serializer for generic types + +When a class, such as `Date` requires a custom serializer, you can apply the `@Serializable` annotation directly to specific types. +This approach is useful when that class is used as a generic type argument, such as in a list of dates: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* +import java.util.Date +import java.text.SimpleDateFormat + +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} + +//sampleStart +@Serializable +class ProgrammingLanguage( + val name: String, + // Applies the custom serializer to a List generic type + val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> +) + +fun main() { + val df = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) + println(Json.encodeToString(data)) + // {"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +## Specify serializers for a file + +To specify a serializer for an entire source code file, use the [`@UseSerializers`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-serializers/) annotation at the beginning of the file: + +```kotlin +@file:UseSerializers(DateAsLongSerializer::class) +``` + +This applies the custom serializer, such as `DateAsLongSerializer`, to all instances of that type within the file, +so you don't need to annotate each property separately: + + + +```kotlin +// Specifies a serializer for the file +@file:UseSerializers(DateAsLongSerializer::class) +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* +import java.util.Date +import java.text.SimpleDateFormat + +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} + +//sampleStart +// No need to specify the custom serializer on the property because it’s applied to the file +@Serializable +class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) + +fun main() { + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(Json.encodeToString(data)) + // {"name":"Kotlin","stableReleaseDate":1455494400000} +} +``` +{kotlin-runnable="true"} + + + + + + + +## Specify custom serializers globally using typealias + +`kotlinx.serialization` requires explicit specification of serialization strategies, typically through the `@Serializable` annotation. +There is no global serializer configuration, except for [contextual serialization](create-custom-serializers.md#contextual-serialization). +However, in larger projects, repeatedly specifying the same serializers, like `Date` across many files can become tedious +when used across many files. + +In such cases, you can use [`typealias`](type-alias.md) to apply custom serializers globally for specific types, +eliminating the need to annotate each occurrence: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* +import java.util.Date +import java.text.SimpleDateFormat +import java.util.TimeZone + +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsLong", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} +//sampleStart +// Defines a serializer that encodes Date as a formatted string (yyyy-MM-dd) +object DateAsSimpleTextSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG) + private val format = SimpleDateFormat("yyyy-MM-dd").apply { + // Sets the time zone to UTC for consistent output + setTimeZone(TimeZone.getTimeZone("UTC")) + } + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value)) + override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString()) +} + +// Applies global serializers using typealias to avoid annotating each occurrence +typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date + +typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date + +// Uses typealiases to apply custom serializers for Date properties +@Serializable +class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) + +fun main() { + val format = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) + println(Json.encodeToString(data)) + // {"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + \ No newline at end of file From 5c42c0b95af0422a84d6a0f8de19f2ca825ae0ee Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Fri, 11 Oct 2024 16:52:18 +0200 Subject: [PATCH 10/14] only links and 2 sections left --- .../alternative-serialization-formats.md | 1740 ++++++++++++++++- docs/topics/create-custom-serializers.md | 377 +--- docs/topics/formats.md | 1 + docs/topics/third-party-classes.md | 90 + 4 files changed, 1922 insertions(+), 286 deletions(-) diff --git a/docs/topics/alternative-serialization-formats.md b/docs/topics/alternative-serialization-formats.md index ca64127590..128b8e7e1b 100644 --- a/docs/topics/alternative-serialization-formats.md +++ b/docs/topics/alternative-serialization-formats.md @@ -1,23 +1,1741 @@ [//]: # (title: Alternative and custom serialization formats) -## CBOR + -## ProtoBuf +Unlike JSON, which is +stable, these are currently experimental features of Kotlin Serialization. -## Create custom serialization formats (experimental) +## CBOR (experimental) -### Implement a basic encoder +Concise Binary Object Representation ([CBOR](https://datatracker.ietf.org/doc/html/rfc7049)) is a compact binary format based on JSON. +It supports a subset of [JSON features](configure-json-serialization.md) and provides a compact representation of data. +While CBOR behaves similarly to JSON, it produces binary output instead of text. -### Implement a basic decoder +To use CBOR in your project, add the CBOR serialization library dependency to your `build.gradle(.kts)` file: -### Encoder and decoder optimization + -#### Add collection support in encoder + -#### Add collection support in decoder +```kotlin +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:%serializationVersion%") +} +``` -#### Handle null values + -### Create an efficient binary format + -### Support format specific types +```text +dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-cbor:%serializationVersion%' +} +``` + + + + + +### Use CBOR for binary serialization + +The [`Cbor`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/) class provides two main functions: + +* [`encodeToByteArray()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-byte-array.html) serializes objects to a binary array. +* [`decodeFromByteArray()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-byte-array.html) deserializes objects from a binary array. + +Let's look at an example where a `Project` object is serialized into a binary array and then deserialized back to its original form: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.cbor.* + + +fun ByteArray.toAsciiHexString() = joinToString("") { + // // Converts bytes to ASCII characters if printable, otherwise shows their hexadecimal value + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + + // Serializes the object into a CBOR binary array + val bytes = Cbor.encodeToByteArray(data) + + // Converts the binary array to a human-readable hex string + println(bytes.toAsciiHexString()) + // {BF}dnameukotlinx.serializationhlanguagefKotlin{FF} + + // Deserializes the binary array back into a Project object + val obj = Cbor.decodeFromByteArray(bytes) + println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) +} +``` + + + + + + + +In [CBOR hex notation](http://cbor.me/), the output of the above example corresponds to the following values: + +| Hex Code | CBOR Type | Description | +|----------|-------------|--------------------------------------| +| BF | map(*) | Start of a CBOR map | +| 64 | text(4) | Length of the string (4 characters) | +| 6E616D65 | string | The string "name" | +| 75 | text(21) | Length of the string (21 characters) | +| 6B6F746C696E782E73657269616C697A6174696F6E | string | The string "kotlinx.serialization" | +| 68 | text(8) | Length of the string (8 characters) | +| 6C616E6775616765 | string | The string "language" | +| 66 | text(6) | Length of the string (6 characters) | +| 4B6F746C696E | string | The string "Kotlin" | +| FF | primitive(*)| End of the CBOR map | + + +> Unlike JSON, CBOR supports maps with non-trivial keys. However, some parsers, like `jackson-dataformat-cbor`, don't support this feature. +> For JSON workaround, see the [Encode structured map keys](serialization-json-configuration.md#encode-structured-map-keys) section. +> +{type="note"} + +### Ignore unknown keys in CBOR + +CBOR is commonly used in communication with [IoT](https://en.wikipedia.org/wiki/Internet_of_things) devices where new properties could be added as a part of a device's +API evolution. By default, unknown keys encountered during deserialization cause an error. +You can configure the deserializer to ignore unknown keys by setting the [`ignoreUnknownKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html) property to `true`: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.cbor.* + +// Sets ignoreUnknownKeys to true to allow unknown keys during deserialization +val format = Cbor { ignoreUnknownKeys = true } + +@Serializable +data class Project(val name: String) + +fun main() { + val data = format.decodeFromHexString( + // CBOR hex notation input with an extra, unknown "language" key + "bf646e616d65756b6f746c696e782e73657269616c697a6174696f6e686c616e6775616765664b6f746c696eff" + ) + // Prints the deserialized Project object, ignoring the language property + println(data) + // Project(name=kotlinx.serialization) +} +``` + +> In the [CBOR hex string](http://cbor.me/), the part representing the unknown "language" property is: +> +> * 68: Length of the key "language" (8 characters). +> * 6c616e6775616765: The key "language". +> * 66: Length of the value "Kotlin" (6 characters). +> * 4b6f746c696e: The value "Kotlin". +> +{type="note"} + + + + + + + +### Customize CBOR encoding + +According to the [RFC 7049 Major Types](https://datatracker.ietf.org/doc/html/rfc7049#section-2.1) specification, CBOR supports the following data types: + +* Major type 0: an unsigned integer +* Major type 1: a negative integer +* Major type 2: a byte string +* Major type 3: a text string +* Major type 4: an array of data items +* Major type 5: a map of pairs of data items +* Major type 6: optional semantic tagging of other major types +* Major type 7: floating-point numbers, simple data types with no content, and the "break" stop code + +By default, Kotlin `ByteArray` instances are encoded as **major type 4**, which represents an array of data items. +You can encode `ByteArray` instances as major type 2, a byte string, by using the [`@ByteString`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/) annotation: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.cbor.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@Serializable +data class Data( + // Encodes the byte array as CBOR major type 2 as a continuous byte string + @ByteString + val type2: ByteArray, + // Encodes the byte array as CBOR major type 4 as an array of individual data items + val type4: ByteArray +) + +fun main() { + // Creates a Data object with two ByteArray fields + val data = Data(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8)) + // Serializes the Data object into a CBOR byte array + val bytes = Cbor.encodeToByteArray(data) + println(bytes.toAsciiHexString()) + // {BF}etype2D{01}{02}{03}{04}etype4{9F}{05}{06}{07}{08}{FF}{FF} + + val obj = Cbor.decodeFromByteArray(bytes) + println(obj) + // Data(type2=[1, 2, 3, 4], type4=[5, 6, 7, 8]) +} +``` + + + + + + + +In addition to encoding `ByteArray` fields, you can customize CBOR serialization for entire classes. +By default, classes are serialized as a CBOR Map, which corresponds to major type 5. +This means that each property of the class is stored as a key-value pair. + +You can serialize a class as a CBOR Array, major type 4, by using the [`@CborArray`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-array/) annotation. +This can be useful for encoding structures like those in [RFC 9052: Basic COSE Structure](https://www.rfc-editor.org/rfc/rfc9052#section-2): + +```kotlin +@Serializable +@CborArray +data class DataClass( +val alg: Int, +val kid: String? +) + +Cbor.encodeToByteArray(DataClass(alg = -7, kid = null)) +``` + +By using annotations like `@ByteString` and `@CborArray`, you can fine-tune how data is encoded, optimizing both binary size and compatibility with existing specifications. + +### Definite and indefinite length encoding in CBOR + +CBOR supports two encodings for maps and arrays: *definite length encoding* and *indefinite length encoding*. +By default, Kotlin Serialization uses indefinite length encoding. +This means that the number of elements in a map or array is not explicitly encoded, and a terminating byte is appended after the last element. + +Definite length encoding works differently by omitting the terminating byte and instead encoding the number of elements at the start of the map or array. +You can switch between these two modes +by using the [`useDefiniteLengthEncoding`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/use-definite-length-encoding.html) property. + +### Tags and labels in CBOR + +CBOR allows defining *tags* to encode additional information for properties and values. You can specify these tags using the +[`@KeyTags`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-key-tags/) and [`@ValueTags`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-value-tags/) annotations. +The `encodeKeyTags`, `encodeValueTags`, `verifyKeyTags`, and `verifyValueTags` properties control +the encoding and verification of these tags. + +> For more information on tagging in CBOR, see [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items). +> +{type="tip"} + +You can also tag entire classes using the [`@ObjectTags`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-object-tags/) annotation, which applies tags to all instances of the class. +When serializing, `@ObjectTags` are always encoded directly before the data of the tagged object. +If a property is both value-tagged and part of an object-tagged type, the value tags precede the object tags. +The `encodeObjectTags` and `verifyObjectTags` properties manage the encoding and verification of these object tags. + +CBOR supports keys of all types, similar to the [`@SerialName`](serialization-customization-options.md#customize-serial-names) annotation. +In the context of COSE (CBOR Object Signing and Encryption), keys are restricted to strings and numbers and are called *labels*. String labels can be +You can assign string labels using the `@SerialName` annotation and number labels using the [`@CborLabel`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-label/) annotation. +The `preferCborLabelsOverNames` property allows prioritizing number labels over serial names when both are present, +optimizing the representation for CBOR while keeping readable names for JSON. + +Kotlin Serialization provides a predefined instance, [`Cbor.CoseCompliant`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/-default/-cose-compliant.html), which follows COSE encoding requirements. +This instance uses definite length encoding, ensures that all tags are encoded and verified, +and prioritizes numeric labels over serial names for more compact representations. + +### Custom CBOR-specific Serializers + +CBOR encoders and decoders implement the [`CborEncoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-encoder/) and [`CborDecoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-decoder/) interfaces. +These interfaces are similar to the general `Encoder` and `Decoder` interfaces, providing access to CBOR-specific configurations through the `cbor` property. +Custom serializers can use this property to access the current `Cbor` instance, embed byte arrays, and adjust settings like `preferCborLabelsOverNames` and `useDefiniteLengthEncoding`. + +For more details on creating custom serializers, see the [Create custom serializers](create-custom-serializers.md) section. + +## ProtoBuf (experimental) + +[Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally +relies on a separate `.proto` file to define the protocol schema. +It is more compact than CBOR, as it assigns integer numbers to fields instead of names. + +To use ProtoBuf in your project, add the ProtoBuf serialization library dependency to your `build.gradle(.kts)` file: + + + + + +```kotlin +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:%serializationVersion%") +} +``` + + + + + +```text +dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:%serializationVersion%' +} +``` + + + + +Kotlin Serialization uses proto2 semantics, where all fields are explicitly required or optional. + +For a basic example, use the +[`ProtoBuf`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/) class with the [`encodeToByteArray()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/encode-to-byte-array.html) and the [`decodeFromByteArray()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/decode-from-byte-array.html) functions: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@Serializable +data class Project(val name: String, val language: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + // Serializes the Project instance into a ProtoBuf byte array + val bytes = ProtoBuf.encodeToByteArray(data) + + // Converts the byte array into a readable hex string for demonstration + println(bytes.toAsciiHexString()) + // {0A}{15}kotlinx.serialization{12}{06}Kotlin + + val obj = ProtoBuf.decodeFromByteArray(bytes) + println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) +} +``` + + + + + + + +In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following: + +```text +Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization" +Field #2: 12 String Length = 6, Hex = 06, UTF8 = "Kotlin" +``` + +### Assign field numbers + +`ProtoBuf` serialization in Kotlin assigns field numbers automatically by default. +To maintain a consistent schema as your data changes, +you can use the [`@ProtoNumber`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-number/) annotation. +This allows you to assign field numbers directly in your data classes, without needing a separate `.proto` file. + +Let's look at an example: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +data class Project( + // Assigns field number 1 to the name property + @ProtoNumber(1) + val name: String, + // Assigns field number 3 to the language property + @ProtoNumber(3) + val language: String +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + val bytes = ProtoBuf.encodeToByteArray(data) + println(bytes.toAsciiHexString()) + // {0A}{15}kotlinx.serialization{1A}{06}Kotlin + val obj = ProtoBuf.decodeFromByteArray(bytes) + println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) +} +``` + +In the output, the `name` property uses field number 1 (`0A`), as specified. The `language` property uses field number 3 (`1A`), skipping the default sequence. + +> For more information about Protobuf field numbers, see the [Official Protobuf Language Guide](https://protobuf.dev/programming-guides/proto2/#assigning). +> +{type="tip"} + + + + + + + +### Specify integer encodings in ProtoBuf + +ProtoBuf supports various integer encodings optimized for different ranges. +You can specify these using the [`@ProtoType`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-type/) annotation and the [`ProtoIntegerType`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/) enum. + +The following example shows all three supported options: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +class Data( + // Uses DEFAULT encoding, optimized for small non-negative numbers + @ProtoType(ProtoIntegerType.DEFAULT) + val a: Int, + // Uses SIGNED encoding, optimized for small signed integers + @ProtoType(ProtoIntegerType.SIGNED) + val b: Int, + // Uses FIXED encoding, which always uses a fixed number of bytes + @ProtoType(ProtoIntegerType.FIXED) + val c: Int +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Data(1, -2, 3) + println(ProtoBuf.encodeToByteArray(data).toAsciiHexString()) + // {08}{01}{10}{03}{1D}{03}{00}{00}{00} +} +``` + +The `ProtoIntegerType` enum supports three options: + +* The [`DEFAULT`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-d-e-f-a-u-l-t/) option uses varint encoding (`intXX`), which is optimized for +small non-negative numbers. For example, the value of `1` is encoded in one byte as `01`. +* The [`SIGNED`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-s-i-g-n-e-d/) option uses signed ZigZag encoding (`sintXX`), making it suitable +for small signed integers. For example, it encodes the value of `-2` in one byte as `03`. +* The [`FIXED`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/) option uses fixed-width encoding (`fixedXX`), which always uses a fixed number of bytes. +For example, it encodes the value of `3` as four bytes `03 00 00 00`. + +> `uintXX` and `sfixedXX` protocol buffer types are not supported. +> +{type="note"} + + + + + + + +### Encode empty lists in ProtoBuf + +ProtoBuf serialization in Kotlin encodes lists and other collections as _repeated fields_. +A repeated field is a way to represent a list or array in ProtoBuf, +where each element of the list is encoded as an individual entry in the serialized data. +This allows ProtoBuf to efficiently handle collections by encoding each item as if it were a separate field with the same tag. + +In ProtoBuf, when a list is empty, it does not produce any elements in the encoded stream for that field. +To ensure that empty lists can be correctly deserialized, you must explicitly specify `emptyList()` as the default +value for properties of collection or map types. +Without this default, an empty list is indistinguishable from a missing field during deserialization, which can cause issues. + +Here's an example: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@Serializable +data class Data( + // Sets default values for the lists to ensure empty lists can be deserialized + val a: List = emptyList(), + val b: List = emptyList() +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Data(listOf(1, 2, 3), listOf()) + val bytes = ProtoBuf.encodeToByteArray(data) + println(bytes.toAsciiHexString()) + // {08}{01}{08}{02}{08}{03} + println(ProtoBuf.decodeFromByteArray(bytes)) + // Data(a=[1, 2, 3], b=[]) +} +``` + +In this example, the list `a` contains three elements, which are encoded in the output, while the list `b` is empty. +The default value of `emptyList()` ensures that `b` is properly recognized as an empty list during deserialization, +rather than being mistaken for a missing field. + + + + + + + +### Packed fields + +In ProtoBuf, _packed fields_ are a way to serialize repeated fields, such as lists of numbers, more efficiently. +Instead of encoding each element with its own tag, a packed field groups all elements into a single entry, +which reduces the overall size of the serialized data. + +You can use the [`@ProtoPacked`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-packed/) annotation +to serialize collection types, excluding maps, as packed fields. +According to the ProtoBuf standard, packed fields are supported only for primitive numeric types, and the annotation is ignored for other types. + +As described in the [format specification](https://developers.google.com/protocol-buffers/docs/encoding#packed), +the ProtoBuf parser automatically handles lists in either packed or repeated format, regardless of the annotation. + +### The oneof field (experimental) + +ProtoBuf serialization in Kotlin supports [oneof](https://protobuf.dev/programming-guides/proto2/#oneof) fields +using Kotlin's [polymorphism](serialization-polymorphism.md) functionality. +This feature enables a field to hold one of several possible types, but only one at a time. + +Consider this ProtoBuf message definition: + +```protobuf +message Data { + required string name = 1; + oneof phone { + string home_phone = 2; + string work_phone = 3; + } +} +``` + +To represent this message in Kotlin: + + +1. Define a class for the entire message, and add a property for the `oneof` interface. Annotate this property with [`@ProtoOneOf`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-one-of/). +2. Create a `sealed interface` or `abstract class` to represent the `oneof` group. +3. Create subclasses for each `oneof` group element, with each subclass having a single property corresponding to its field. +4. Annotate these properties with `@ProtoNumber` according to their field numbers in the `oneof` definition. + +Let's look at an example: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +// Defines the data class with a oneof property +@OptIn(ExperimentalSerializationApi::class) +@Serializable +data class Data( + // Represents the name field with field number 1 + @ProtoNumber(1) val name: String, + // Uses the IPhoneType interface for the oneof phone group + @ProtoOneOf val phone: IPhoneType?, +) + +// The sealed interface representing the 'oneof' group +@Serializable sealed interface IPhoneType + +// Represents the home_phone field from the oneof group +@OptIn(ExperimentalSerializationApi::class) +@Serializable @JvmInline value class HomePhone(@ProtoNumber(2) val number: String): IPhoneType + +// Represents the work_phone field from the oneof group +@OptIn(ExperimentalSerializationApi::class) +@Serializable data class WorkPhone(@ProtoNumber(3) val number: String): IPhoneType + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val dataTom = Data("Tom", HomePhone("123")) + val stringTom = ProtoBuf.encodeToHexString(dataTom) + val dataJerry = Data("Jerry", WorkPhone("789")) + val stringJerry = ProtoBuf.encodeToHexString(dataJerry) + println(stringTom) + // 0a03546f6d1203313233 + println(stringJerry) + // 0a054a657272791a03373839 + println(ProtoBuf.decodeFromHexString(stringTom)) + // Data(name=Tom, phone=HomePhone(number=123)) + println(ProtoBuf.decodeFromHexString(stringJerry)) + // Data(name=Jerry, phone=WorkPhone(number=789)) +} +``` + + + + + + + +The output shows how each oneof type is encoded: + +* `0a03546f6d1203313233` represents "Tom" with a home phone. +* `0a054a657272791a03373839` represents "Jerry" with a work phone. + +> Each `oneof` group must be tied to a single data class to prevent ID conflicts or runtime exceptions. +> +{type="note"} + +You can also define a class without the `@ProtoOneOf` annotation if you plan to use it only for deserialization. + +For example: + +```protobuf +@Serializable +data class Data2( + @ProtoNumber(1) val name: String, + @ProtoNumber(2) val homeNumber: String? = null, + @ProtoNumber(3) val workNumber: String? = null, +) +``` + +This approach allows deserializing the same input as `Data`. +However, it doesn't enforce exclusivity between `homeNumber` and `workNumber`. +If both fields have values or are both `null`, the serialized output may not comply with the original schema, +which could cause compatibility issues when parsed by other ProtoBuf tools. + +### Generate a ProtoBuf schema (experimental) + +Typically, working with ProtoBuf involves using a `.proto` file and a code generator to create code for serialization and deserialization. +However, with Kotlin Serialization, you can use Kotlin classes annotated with `@Serializable` as the source for the schema, making `.proto` files optional. + +This approach simplifies the process when all the code involved is written in Kotlin, +but it can complicate interoperability with other languages. +To address this, you can use the [`ProtoBufSchemaGenerator`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema/-proto-buf-schema-generator/) to generate a `.proto` file from your Kotlin classes. +This allows you to maintain Kotlin classes as the primary source while using traditional ProtoBuf tools for other languages. + +Here’s an example that generates a `.proto` schema from a Kotlin data class: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* +import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator + +@Serializable +data class SampleData( + val amount: Long, + val description: String?, + val department: String = "QA" +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val descriptors = listOf(SampleData.serializer().descriptor) + val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors) + println(schemas) +} +``` + + + +This code generates the following `.proto` schema: + +```text +syntax = "proto2"; + + +// serial name 'example.exampleFormats09.SampleData' +message SampleData { + required int64 amount = 1; + optional string description = 2; + // WARNING: a default value decoded when value is missing + optional string department = 3; +} +``` + + + +> Default values aren't represented in `.proto` files, so the schema includes a warning when they are present. +> +{type="note"} + +## Properties (experimental) + +Kotlin Serialization can serialize a class into a flat map with `String` keys using +the [`Properties`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-properties/kotlinx.serialization.properties/-properties/) format implementation. + +To use the `Properties` format in your project, add the properties serialization library dependency to your `build.gradle(.kts)` file: + + + + + +```kotlin +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-properties:%serializationVersion%") +} +``` + + + + + +```groovy +dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-properties:%serializationVersion%' +} +``` + + + + + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.properties.Properties +import kotlinx.serialization.properties.* + +@Serializable +class Project(val name: String, val owner: User) + +@Serializable +class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", User("kotlin")) + // Encodes the data object into a map with dot-separated keys for nested properties + val map = Properties.encodeToMap(data) + // Iterates through the map and prints the key-value pairs + map.forEach { (k, v) -> println("$k = $v") } + // name = kotlinx.serialization + // owner.name = kotlin +} +``` + + + + + + + +## Create custom formats (experimental) + +To implement a custom format in Kotlin Serialization, +you need to provide custom implementations for the [`Encoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/) and [`Decoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/) interfaces. +These interfaces are extensive, but the [`AbstractEncoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/) and [`AbstractDecoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/) classes can simplify the process. + +The `AbstractEncoder` class provides default implementations for most of the encode functions, such as [`encodeString()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/encode-string.html), +which delegate to the `encodeValue(value: Any)` function. +This means that by overriding the `encodeValue()` function, you can create a basic, functional custom format with minimal effort. + +### Create a basic encoder + +To implement custom serialization behavior in Kotlin, you can create a custom `Encoder` by extending the `AbstractEncoder` class. +This allows you to control how each value is serialized during the encoding process. + +To create a basic encoder: + +1. Create a class that extends `AbstractEncoder` to define custom serialization logic. +2. Override the `encodeValue()` function to specify how each value should be handled. +3. Write a function that uses your custom encoder to serialize an object. +4. Add an inline version of this function to make it easier to use with different types. + +> Since encoders are usually used by other parts of an application, +> it is recommended to propagate the `@ExperimentalSerializationApi` annotation rather than opting in only within specific functions. +> +{type="note"} + +Here's an example that encodes data into a list: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +// Creates a custom encoder that stores serialized values in a list +@ExperimentalSerializationApi +class ListEncoder : AbstractEncoder() { + val list = mutableListOf() + + override val serializersModule: SerializersModule = EmptySerializersModule() + + // Adds the value to the list + override fun encodeValue(value: Any) { + list.add(value) + } +} + +// Serializes an object into a list of its values using the custom encoder +@ExperimentalSerializationApi +fun encodeToList(serializer: SerializationStrategy, value: T): List { + val encoder = ListEncoder() + encoder.encodeSerializableValue(serializer, value) + return encoder.list +} + +// Provides a type-safe, inline version of encodeToList for convenience +// Uses a reified type parameter to automatically retrieve the correct serializer +@ExperimentalSerializationApi +inline fun encodeToList(value: T) = encodeToList(serializer(), value) + +@Serializable +data class Project(val name: String, val owner: User, val votes: Int) + +@Serializable +data class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", User("kotlin"), 9000) + println(encodeToList(data)) + // [kotlinx.serialization, kotlin, 9000] +} +``` + +This example demonstrates how to create a custom `ListEncoder` to transform a Kotlin object into a list of its individual values, +maintaining the order they are defined in the class. + + + + + + + +### Create a basic decoder + +To decode custom serialized data in Kotlin, you can create a custom `Decoder` by extending the `AbstractDecoder` class. +This allows you to control how each value is deserialized during the decoding process. + +To create a basic decoder: + +1. Create a class that extends `AbstractDecoder` to define custom deserialization logic. +2. Override the [`decodeValue()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/decode-value.html) function to provide how values should be read during deserialization. +3. Override the [`decodeElementIndex()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html) function to manage the order of deserialized values, returning the next index with each call. +4. Override the [`beginStructure()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/begin-structure.html) function to manage nested structures, ensuring each maintains its own state. +5. Write a function that uses your custom decoder to deserialize an object. +6. Optionally, add an inline version of this function to make it easier to use with different types. + +Let's look at an example where the list is decoded back into an object: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +// Creates a custom encoder that stores serialized values in a list +@ExperimentalSerializationApi +class ListEncoder : AbstractEncoder() { + val list = mutableListOf() + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun encodeValue(value: Any) { + list.add(value) + } +} + +// Serializes an object into a list of its values using the custom encoder +@ExperimentalSerializationApi +fun encodeToList(serializer: SerializationStrategy, value: T): List { + val encoder = ListEncoder() + encoder.encodeSerializableValue(serializer, value) + return encoder.list +} + +// Provides a type-safe, inline version of encodeToList for convenience +@ExperimentalSerializationApi +inline fun encodeToList(value: T) = encodeToList(serializer(), value) + +// Creates a custom decoder that deserializes values from a list +@ExperimentalSerializationApi +class ListDecoder(val list: ArrayDeque) : AbstractDecoder() { + private var elementIndex = 0 + + override val serializersModule: SerializersModule = EmptySerializersModule() + + // Retrieves the next value from the list + override fun decodeValue(): Any = list.removeFirst() + + // Manages the decoding order of values, returning the next index + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + ListDecoder(list) +} + +// Deserializes an object from a list using the custom decoder +@ExperimentalSerializationApi +fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { + val decoder = ListDecoder(ArrayDeque(list)) + return decoder.decodeSerializableValue(deserializer) +} + +@ExperimentalSerializationApi +inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) + +@Serializable +data class Project(val name: String, val owner: User, val votes: Int) + +@Serializable +data class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", User("kotlin"), 9000) + val list = encodeToList(data) + println(list) + // [kotlinx.serialization, kotlin, 9000] + val obj = decodeFromList(list) + println(obj) + // Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000) +} +``` + +In the example above, a custom `ListDecoder` is created by extending `AbstractDecoder`. +The decoder reads values from a list, using the `decodeElementIndex()` function to track the current index and ensure values are deserialized in order. +The `beginStructure()` function manages nested objects during deserialization. + + + + + + + +### Custom encoder and decoder optimization + +The following sections focus on improving the `ListEncoder` and `ListDecoder` implementations introduced in the [Create a basic encoder](#create-a-basic-encoder) and [Create a basic decoder](#create-a-basic-decoder) sections. +By building on the foundations of the `ListEncoder` and `ListDecoder` examples, these optimizations +provide a more robust and versatile custom serialization setup, suitable for a wider range of data structures and scenarios. + +#### Optimize with sequential decoding + +To optimize a custom decoder for formats that store elements in order, use the [`decodeSequentially()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html) function. +When this function returns `true`, it signals that the decoder can handle elements in sequence without needing manual index tracking. +This can improve performance when deserializing data stored in sequential formats: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +@ExperimentalSerializationApi +class ListEncoder : AbstractEncoder() { + val list = mutableListOf() + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun encodeValue(value: Any) { + list.add(value) + } +} + +@ExperimentalSerializationApi +fun encodeToList(serializer: SerializationStrategy, value: T): List { + val encoder = ListEncoder() + encoder.encodeSerializableValue(serializer, value) + return encoder.list +} + +@ExperimentalSerializationApi +inline fun encodeToList(value: T) = encodeToList(serializer(), value) + + +// Creates a custom decoder that supports sequential decoding +@ExperimentalSerializationApi +class ListDecoder(val list: ArrayDeque) : AbstractDecoder() { + private var elementIndex = 0 + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun decodeValue(): Any = list.removeFirst() + + // Manages the decoding order of values, returning the next index + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + ListDecoder(list) + + // Indicates support for sequential decoding for improved performance + override fun decodeSequentially(): Boolean = true +} + + +@ExperimentalSerializationApi +fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { + val decoder = ListDecoder(ArrayDeque(list)) + return decoder.decodeSerializableValue(deserializer) +} + +@ExperimentalSerializationApi +inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) + +@Serializable +data class Project(val name: String, val owner: User, val votes: Int) + +@Serializable +data class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", User("kotlin"), 9000) + val list = encodeToList(data) + println(list) + // [kotlinx.serialization, kotlin, 9000] + val obj = decodeFromList(list) + println(obj) + // Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000) +} +``` + + + + + +#### Add collection support + +To add collection support to a custom format, you need to manage how collection sizes are encoded and decoded. +While a basic format might handle individual elements, +it must also track the size of collections to ensure accurate deserialization of collections and maps. + +To add support for collections in a custom format: + +1. Implement the [`beginCollection()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/begin-collection.html) function in the encoder to handle the collection size. +2. Return the encoder instance from the `beginCollection()` function if no additional state is needed. +3. Implement the [`decodeCollectionSize()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-collection-size.html) function in the decoder to retrieve and store the collection size during deserialization. +4. Ensure that the `decodeSequentially()` function returns `true` if the format stores collection size in advance. + +Here's how you can add collection support to the `ListEncoder` example: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +@ExperimentalSerializationApi +class ListEncoder : AbstractEncoder() { + val list = mutableListOf() + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun encodeValue(value: Any) { + list.add(value) + } + + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + encodeInt(collectionSize) + return this + } +} + +@ExperimentalSerializationApi +fun encodeToList(serializer: SerializationStrategy, value: T): List { + val encoder = ListEncoder() + encoder.encodeSerializableValue(serializer, value) + return encoder.list +} + +@ExperimentalSerializationApi +inline fun encodeToList(value: T) = encodeToList(serializer(), value) + +@ExperimentalSerializationApi +class ListDecoder(val list: ArrayDeque, var elementsCount: Int = 0) : AbstractDecoder() { + private var elementIndex = 0 + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun decodeValue(): Any = list.removeFirst() + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + ListDecoder(list, descriptor.elementsCount) + + override fun decodeSequentially(): Boolean = true + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = + decodeInt().also { elementsCount = it } +} + +@ExperimentalSerializationApi +fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { + val decoder = ListDecoder(ArrayDeque(list)) + return decoder.decodeSerializableValue(deserializer) +} + +@ExperimentalSerializationApi +inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) + +@Serializable +data class Project(val name: String, val owners: List, val votes: Int) + +@Serializable +data class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", listOf(User("kotlin"), User("jetbrains")), 9000) + val list = encodeToList(data) + println(list) + // [kotlinx.serialization, 2, kotlin, jetbrains, 9000] + val obj = decodeFromList(list) + println(obj) + // Project(name=kotlinx.serialization, owners=[User(name=kotlin), User(name=jetbrains)], votes=9000) +} +``` + +This example demonstrates adding collection support to the ListEncoder and ListDecoder: + +* `beginCollection()` in `ListEncoder` encodes the collection size, ensuring that the size is recorded alongside its elements. +* `decodeCollectionSize()` in `ListDecoder` reads this encoded size during deserialization, allowing the decoder to reconstruct collections and maps accurately. + +This setup enables precise encoding and decoding of collections in a custom format. + + + + + + + +#### Add null support + +To handle `null` values in a custom format, you need to introduce a way to indicate when a value is `null` or not. +This typically involves adding a "null indicator" that distinguishes between `null` values and actual data. + +To add support for null values in a custom format: + +1. Override [`encodeNull()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/encode-null.html) in the encoder to specify how `null` should be represented during serialization. +2. Override [`encodeNotNullMark()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-not-null-mark.html) in the encoder to indicate non-null values. +3. Override [`decodeNotNullMark()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-not-null-mark.html) in the decoder to differentiate between null and non-null values during deserialization. + +Here's an example that adds `null` support to the `ListEncoder` and `ListDecoder` implementations: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +@ExperimentalSerializationApi +class ListEncoder : AbstractEncoder() { + val list = mutableListOf() + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun encodeValue(value: Any) { + list.add(value) + } + + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + encodeInt(collectionSize) + return this + } + + // Represents null values as NULL + override fun encodeNull() = encodeValue("NULL") + // Represents non-null values with "!!" + override fun encodeNotNullMark() = encodeValue("!!") +} + +@ExperimentalSerializationApi +fun encodeToList(serializer: SerializationStrategy, value: T): List { + val encoder = ListEncoder() + encoder.encodeSerializableValue(serializer, value) + return encoder.list +} + +@ExperimentalSerializationApi +inline fun encodeToList(value: T) = encodeToList(serializer(), value) + +// Creates a custom decoder that handles null values +@ExperimentalSerializationApi +class ListDecoder(val list: ArrayDeque, var elementsCount: Int = 0) : AbstractDecoder() { + private var elementIndex = 0 + + override val serializersModule: SerializersModule = EmptySerializersModule() + + override fun decodeValue(): Any = list.removeFirst() + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + ListDecoder(list, descriptor.elementsCount) + + override fun decodeSequentially(): Boolean = true + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = + decodeInt().also { elementsCount = it } + + // Checks if the next value is "NULL" to determine if it's actually null + override fun decodeNotNullMark(): Boolean = decodeString() != "NULL" +} + +@ExperimentalSerializationApi +fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { + val decoder = ListDecoder(ArrayDeque(list)) + return decoder.decodeSerializableValue(deserializer) +} + +@ExperimentalSerializationApi +inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) + +@Serializable +data class Project(val name: String, val owner: User?, val votes: Int?) + +@Serializable +data class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", User("kotlin"), null) + val list = encodeToList(data) + println(list) + // [kotlinx.serialization, !!, kotlin, NULL] + val obj = decodeFromList(list) + println(obj) + // Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=null) +} +``` + + + + + + + +### Create a compact binary format + +Binary formats are often used for their compact representation of data, making them ideal for scenarios where minimizing storage size or transmission bandwidth is important. +Custom binary formats allow you to control how data is serialized and deserialized at a low level, +providing flexibility to optimize performance and compatibility with other systems. + +In the following example, a custom encoder and decoder are implemented using +[`java.io.DataOutput`](https://docs.oracle.com/javase/8/docs/api/java/io/DataOutput.html) and [`java.io.DataInput`](https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html) interfaces. +This approach enables detailed control over the serialization of each [primitive type](serialization.md#supported-formats). + +First, override the encode functions for each primitive type, such as `encodeInt()` for integers or `encodeString()` for strings. +This allows you to write the binary data directly to the `DataOutput` stream: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* +import java.io.* + +@ExperimentalSerializationApi +class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() { + override val serializersModule: SerializersModule = EmptySerializersModule() + override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) + override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) + override fun encodeShort(value: Short) = output.writeShort(value.toInt()) + override fun encodeInt(value: Int) = output.writeInt(value) + override fun encodeLong(value: Long) = output.writeLong(value) + override fun encodeFloat(value: Float) = output.writeFloat(value) + override fun encodeDouble(value: Double) = output.writeDouble(value) + override fun encodeChar(value: Char) = output.writeChar(value.code) + override fun encodeString(value: String) = output.writeUTF(value) + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index) + + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + encodeInt(collectionSize) + return this + } + + override fun encodeNull() = encodeBoolean(false) + override fun encodeNotNullMark() = encodeBoolean(true) +} + +@ExperimentalSerializationApi +fun encodeTo(output: DataOutput, serializer: SerializationStrategy, value: T) { + val encoder = DataOutputEncoder(output) + encoder.encodeSerializableValue(serializer, value) +} + +@ExperimentalSerializationApi +inline fun encodeTo(output: DataOutput, value: T) = encodeTo(output, serializer(), value) +``` + +Next, implement the decode functions for each primitive type, such as `decodeInt()` or `decodeString()`. +This allows reading binary data from the `DataInput` stream and reconstructing the original data structure: + +```kotlin +@ExperimentalSerializationApi +class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() { + private var elementIndex = 0 + override val serializersModule: SerializersModule = EmptySerializersModule() + override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 + override fun decodeByte(): Byte = input.readByte() + override fun decodeShort(): Short = input.readShort() + override fun decodeInt(): Int = input.readInt() + override fun decodeLong(): Long = input.readLong() + override fun decodeFloat(): Float = input.readFloat() + override fun decodeDouble(): Double = input.readDouble() + override fun decodeChar(): Char = input.readChar() + override fun decodeString(): String = input.readUTF() + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt() + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + DataInputDecoder(input, descriptor.elementsCount) + + override fun decodeSequentially(): Boolean = true + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = + decodeInt().also { elementsCount = it } + + override fun decodeNotNullMark(): Boolean = decodeBoolean() +} + + +@ExperimentalSerializationApi +fun decodeFrom(input: DataInput, deserializer: DeserializationStrategy): T { + val decoder = DataInputDecoder(input) + return decoder.decodeSerializableValue(deserializer) +} + +@ExperimentalSerializationApi +inline fun decodeFrom(input: DataInput): T = decodeFrom(input, serializer()) + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} +``` + +Finally, use these custom encoder and decoder classes to serialize and deserialize Kotlin objects to a binary format. +You can now serialize and deserialize arbitrary data. Here is an example using the `Project` data class: + +```kotlin +@Serializable +data class Project(val name: String, val language: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + val output = ByteArrayOutputStream() + encodeTo(DataOutputStream(output), data) + val bytes = output.toByteArray() + println(bytes.toAsciiHexString()) + // {00}{15}kotlinx.serialization{00}{06}Kotlin + val input = ByteArrayInputStream(bytes) + val obj = decodeFrom(DataInputStream(input)) + println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) +} +``` + +The result is a compact binary representation of the `Project` class that only includes the essential data. +This makes it easy to customize for specific needs where small size and efficiency are important. + + + + + + + +### Add support for format-specific types + +Custom binary formats may need to handle data types that are not part of the standard primitives in Kotlin Serialization. +For example, a format may require a more efficient way to serialize byte arrays. +You can achieve this by overriding the [`encodeSerializableValue(serializer, value)`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html) function in the encoder. + +In the following example, a specialized approach is used for `ByteArray` types, +utilizing Java's [`DataOutput`](https://docs.oracle.com/javase/8/docs/api/java/io/DataOutput.html) methods for efficient serialization. +By referencing the `serializer.descriptor` property, the encoder can determine when a `ByteArray` is being serialized and apply the appropriate logic for more efficient processing: + +> This an important difference. This way our format implementation properly supports +> [Custom serializers](create-custom-serializers.md) that a user might specify for a type that just happens +> to be internally represented as a byte array, but need a different serial representation. + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.modules.* +import kotlinx.serialization.encoding.* +import java.io.* + +private val byteArraySerializer = serializer() +``` + +> Alternatively, you can use the built-in [`ByteArraySerializer()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-byte-array-serializer.html) function for similar functionality. +> +{type="note"} + +We add the corresponding code to the [Encoder] implementation of our +[Efficient binary format](#efficient-binary-format). To make our `ByteArray` encoding even more efficient, +we add a trivial implementation of `encodeCompactSize` function that uses only one byte to represent +a size of up to 254 bytes. + +```kotlin +@ExperimentalSerializationApi +class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() { + override val serializersModule: SerializersModule = EmptySerializersModule() + override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) + override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) + override fun encodeShort(value: Short) = output.writeShort(value.toInt()) + override fun encodeInt(value: Int) = output.writeInt(value) + override fun encodeLong(value: Long) = output.writeLong(value) + override fun encodeFloat(value: Float) = output.writeFloat(value) + override fun encodeDouble(value: Double) = output.writeDouble(value) + override fun encodeChar(value: Char) = output.writeChar(value.code) + override fun encodeString(value: String) = output.writeUTF(value) + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index) + + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + encodeInt(collectionSize) + return this + } + + override fun encodeNull() = encodeBoolean(false) + override fun encodeNotNullMark() = encodeBoolean(true) + + override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { + if (serializer.descriptor == byteArraySerializer.descriptor) + encodeByteArray(value as ByteArray) + else + super.encodeSerializableValue(serializer, value) + } + + private fun encodeByteArray(bytes: ByteArray) { + encodeCompactSize(bytes.size) + output.write(bytes) + } + + private fun encodeCompactSize(value: Int) { + if (value < 0xff) { + output.writeByte(value) + } else { + output.writeByte(0xff) + output.writeInt(value) + } + } +} + +@ExperimentalSerializationApi +fun encodeTo(output: DataOutput, serializer: SerializationStrategy, value: T) { + val encoder = DataOutputEncoder(output) + encoder.encodeSerializableValue(serializer, value) +} + +@ExperimentalSerializationApi +inline fun encodeTo(output: DataOutput, value: T) = encodeTo(output, serializer(), value) + +@ExperimentalSerializationApi +class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() { + private var elementIndex = 0 + override val serializersModule: SerializersModule = EmptySerializersModule() + override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 + override fun decodeByte(): Byte = input.readByte() + override fun decodeShort(): Short = input.readShort() + override fun decodeInt(): Int = input.readInt() + override fun decodeLong(): Long = input.readLong() + override fun decodeFloat(): Float = input.readFloat() + override fun decodeDouble(): Double = input.readDouble() + override fun decodeChar(): Char = input.readChar() + override fun decodeString(): String = input.readUTF() + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt() + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + DataInputDecoder(input, descriptor.elementsCount) + + override fun decodeSequentially(): Boolean = true + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = + decodeInt().also { elementsCount = it } + + override fun decodeNotNullMark(): Boolean = decodeBoolean() +``` + +Similarly, override the `Decoder` implementation's [`decodeSerializableValue()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html) function to handle the efficient reading of `ByteArray` data: + +```kotlin + @Suppress("UNCHECKED_CAST") + override fun decodeSerializableValue(deserializer: DeserializationStrategy, previousValue: T?): T = + if (deserializer.descriptor == byteArraySerializer.descriptor) + decodeByteArray() as T + else + super.decodeSerializableValue(deserializer, previousValue) + + private fun decodeByteArray(): ByteArray { + val bytes = ByteArray(decodeCompactSize()) + input.readFully(bytes) + return bytes + } + + private fun decodeCompactSize(): Int { + val byte = input.readByte().toInt() and 0xff + if (byte < 0xff) return byte + return input.readInt() + } +} + +@ExperimentalSerializationApi +fun decodeFrom(input: DataInput, deserializer: DeserializationStrategy): T { +val decoder = DataInputDecoder(input) +return decoder.decodeSerializableValue(deserializer) +} + +@ExperimentalSerializationApi +inline fun decodeFrom(input: DataInput): T = decodeFrom(input, serializer()) + +fun ByteArray.toAsciiHexString() = joinToString("") { +if (it in 32..127) it.toInt().toChar().toString() else +"{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} +``` + +Now, you can serialize and deserialize objects with embedded `ByteArray` data: + +```kotlin +@Serializable +data class Project(val name: String, val attachment: ByteArray) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", byteArrayOf(0x0A, 0x0B, 0x0C, 0x0D)) + val output = ByteArrayOutputStream() + encodeTo(DataOutputStream(output), data) + val bytes = output.toByteArray() + println(bytes.toAsciiHexString()) + // {00}{15}kotlinx.serialization{04}{0A}{0B}{0C}{0D} + val input = ByteArrayInputStream(bytes) + val obj = decodeFrom(DataInputStream(input)) + println(obj) + // Project(name=kotlinx.serialization, attachment=[10, 11, 12, 13]) +} +``` + +As we can see, our custom byte array format is being used, with the compact encoding of its size in one byte. + + + + + + + +### Base64 + +To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default +implementation of Kotlin's Base64 encoder. Note that some serializers use different RFCs for Base64 encoding by default. +For example, Jackson uses a variant of [Base64 Mime](https://datatracker.ietf.org/doc/html/rfc2045). The same result in +kotlinx.serialization can be achieved with Base64.Mime encoder. +[Kotlin's documentation for Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) lists +other available encoders. + +```kotlin +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.descriptors.* +import kotlin.io.encoding.* + +@OptIn(ExperimentalEncodingApi::class) +object ByteArrayAsBase64Serializer : KSerializer { + private val base64 = Base64.Default + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor( + "ByteArrayAsBase64Serializer", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: ByteArray) { + val base64Encoded = base64.encode(value) + encoder.encodeString(base64Encoded) + } + + override fun deserialize(decoder: Decoder): ByteArray { + val base64Decoded = decoder.decodeString() + return base64.decode(base64Decoded) + } +} +``` + +For more details on how to create your own custom serializer, you can +see [custom serializers](serializers.md#custom-serializers). + +Then we can use it like this: + +```kotlin +@Serializable +data class Value( + @Serializable(with = ByteArrayAsBase64Serializer::class) + val base64Input: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as Value + return base64Input.contentEquals(other.base64Input) + } + + override fun hashCode(): Int { + return base64Input.contentHashCode() + } +} + +fun main() { + val string = "foo string" + val value = Value(string.toByteArray()) + val encoded = Json.encodeToString(value) + println(encoded) + val decoded = Json.decodeFromString(encoded) + println(decoded.base64Input.decodeToString()) +} +``` + + + + + +Notice the serializer we wrote is not dependent on `Json` format, therefore, it can be used in any format. + +For projects that use this serializer in many places, to avoid specifying the serializer every time, it is possible +to [specify a serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias). +For example: + +````kotlin +typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray +```` + + diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index f6a7ffc63c..c9d06b1c89 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -37,9 +37,9 @@ Color(rgb: kotlin.Int) ## Create a custom primitive serializer If you need more control over how your data is encoded and decoded, you can create a custom serializer. -This allows you to customize how your data is represented, such as converting an RGB integer value into a hexadecimal string. +This allows you to customize how your data is represented, such as by converting an RGB integer value into a hexadecimal string. -Custom serializers implement the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) interface and +Custom serializers implement the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) interface, allowing you to control how your data is transformed between its Kotlin object form and its serialized form. To create a custom primitive serializer: @@ -195,7 +195,7 @@ This allows you to enforce specific constraints, like ensuring that property val The surrogate class can also have a [custom `@SerialName`](serialization-customization-options.md#customize-serial-names) annotation, making it indistinguishable from the original class when formats rely on class names. -Like with [delegated serialization]((#delegate-serialization-to-another-serializer)), surrogate classes also rely on reusing existing serialization logic. +Just like [delegated serialization](#delegate-serialization-to-another-serializer), surrogate classes also rely on reusing existing serialization logic. However, instead of transforming or restructuring individual fields within the same class, you create a new surrogate class to represent the serialized form. @@ -204,7 +204,7 @@ After defining the surrogate class, you can retrieve the plugin-generated serial The function reuses the automatically generated [`SerialDescriptor`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/) of the surrogate class to describe the structure of the serialized data while maintaining compatibility with the original class. -To serialize with the surrogate class, delegate the serialization process to its serializer by using the `encodeSerializableValue()` and `decodeSerializableValue()` functions: +To serialize with the surrogate class, delegate the serialization process to its serializer using the `encodeSerializableValue()` and `decodeSerializableValue()` functions: ```kotlin // Imports the necessary libraries @@ -452,35 +452,40 @@ fun main() { {"r":0,"g":255,"b":0} --> -## Create a custom serializer for a generic type +## Create a custom serializer for generic types -Let us take a look at the following example of the generic `Box` class. -It is marked with `@Serializable(with = BoxSerializer::class)` as we plan to have a custom serialization -strategy for it. +When creating a custom serializer for a generic class, you must provide the appropriate `KSerializer` for each generic parameter. +This ensures that the correct serialization strategy is applied to each type used within the generic class. -```kotlin -@Serializable(with = BoxSerializer::class) -data class Box(val contents: T) -``` +To define a custom serializer for a generic class: + +1. Mark the class with `@Serializable(with = YourSerializer::class)`. +2. Create a custom serializer as a `class`, which accepts one or more `KSerializer` instances for its generic parameters. +3. Delegate the serialization logic to the provided `KSerializer` for each type parameter during serialization and deserialization. -An implementation of [KSerializer] for a regular type is written as an `object`, as we saw in this chapter's -examples for the `Color` type. A generic class serializer is instantiated with serializers -for its generic parameters. We saw this in the [Plugin-generated generic serializer](#plugin-generated-generic-serializer) section. -A custom serializer for a generic class must be a `class` with a constructor that accepts as many [KSerializer] -parameters as the type has generic parameters. Let us write a `Box` serializer that erases itself during -serialization, delegating everything to the underlying serializer of its `data` property. +Let's look at an example using a generic `Box` class: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* + +//sampleStart +// Marks the Box class with @Serializable and specifies a custom serializer +@Serializable(with = BoxSerializer::class) +data class Box(val contents: T) + +// Creates a custom serializer as a class for Box class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer> { + // Uses the descriptor from the provided KSerializer to define the structure of Box override val descriptor: SerialDescriptor = dataSerializer.descriptor + // Delegates serialization and deserialization override fun serialize(encoder: Encoder, value: Box) = dataSerializer.serialize(encoder, value.contents) override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) } -``` - -Now we can serialize and deserialize `Box`. -```kotlin @Serializable data class Project(val name: String) @@ -488,328 +493,150 @@ fun main() { val box = Box(Project("kotlinx.serialization")) val string = Json.encodeToString(box) println(string) + // {"name":"kotlinx.serialization"} println(Json.decodeFromString>(string)) + // Box(contents=Project(name=kotlinx.serialization)) } +//sampleEnd ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-serializer-19.kt). - -The resulting JSON looks like the `Project` class was serialized directly. + + -## Format-specific serializers - -The above custom serializers worked in the same way for every format. However, there might be format-specific -features that a serializer implementation would like to take advantage of. - -* The [Json transformations](json.md#json-transformations) section of the [Json](json.md) chapter provides examples - of serializers that utilize JSON-specific features. - -* A format implementation can have a format-specific representation for a type as explained - in the [Format-specific types](formats.md#format-specific-types) section of - the [Alternative and custom formats (experimental)](formats.md) chapter. +## Implement contextual serialization -This chapter proceeds with a generic approach to tweaking the serialization strategy based on the context. +_Contextual serialization_ allows you to adjust the serialization strategy for specific types at runtime, based on the context in which they are used. +Unlike static serialization, +which is fully defined at compile-time, contextual serialization lets you modify how objects are serialized deep within an object tree. +For example, you could serialize `java.util.Date` in JSON format either as an ISO 8601 String or as a Long, depending on the protocol version being used with contextual serialization. +This approach is supported by the built-in [`ContextualSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual-serializer/) class. -## Contextual serialization +To implement contextual serialization: -All the previous approaches to specifying custom serialization strategies were _static_, that is -fully defined at compile-time. The exception was the [Passing a serializer manually](#passing-a-serializer-manually) -approach, but it worked only on a top-level object. You might need to change the serialization -strategy for objects deep in the serialized object tree at run-time, with the strategy being selected in a context-dependent way. -For example, you might want to represent `java.util.Date` in JSON format as an ISO 8601 string or as a long integer -depending on a version of a protocol you are serializing data for. This is called _contextual_ serialization, and it -is supported by a built-in [ContextualSerializer] class. Usually we don't have to use this serializer class explicitly—there -is the [Contextual] annotation providing a shortcut to -the `@Serializable(with = ContextualSerializer::class)` annotation, -or the [UseContextualSerialization] annotation can be used at the file-level just like -the [UseSerializers] annotation. Let's see an example utilizing the former. +1. Define a custom `KSerializer` for the type you want to serialize dynamically. This controls how the type is serialized and deserialized at runtime. +2. Mark the property in your serializable class with the [`@Contextual`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual/) annotation . - - -```kotlin -@Serializable -class ProgrammingLanguage( - val name: String, - @Contextual - val stableReleaseDate: Date -) -``` + > The [`@Contextual`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual/) annotation is a convenient shortcut for using the [`ContextualSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual-serializer/) class, which enables contextual serialization. + > It is equivalent to using @Serializable(with = ContextualSerializer::class). + > Alternatively, you can also apply the [`@UseContextualSerialization`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-contextual-serialization/) annotation at the file level to apply contextual serialization across multiple properties in the same file, similar to [how @UseSerializers works]((third-party-classes.md#specify-serializers-for-a-file)). + > + {type="note"} - + > Without the `SerializersModule`, a `SerializationException` is thrown during the serialization or deserialization of contextually annotated types. + > + {type="note"} -To actually serialize this class we must provide the corresponding context when calling the `encodeToXxx`/`decodeFromXxx` -functions. Without it we'll get a "Serializer for class 'Date' is not found" exception. +4. Create a `Json` instance and pass the `SerializersModule` to the [`serializersModule`]((https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/serializers-module.html)) property. This ensures that the correct contextual serializer is used for serialization. -> See [here](../../guide/example/example-serializer-20.kt) for an example that produces that exception. + > For more information about configuring a Json instance, see the [JSON configuration](serialization-json-configuration.md) section. + > + {type="tip"} - +Let's look at an example: - - -### Serializers module -To provide a context, we define a [SerializersModule] instance that describes which serializers shall be used -at run-time to serialize which contextually-serializable classes. This is done using the -[SerializersModule {}][SerializersModule()] builder function, which provides the [SerializersModuleBuilder] DSL to -register serializers. In the below example we use the [contextual][_contextual] function with the serializer. The corresponding -class this serializer is defined for is fetched automatically via the `reified` type parameter. - -```kotlin +// Defines the SerializersModule and registers DateAsLongSerializer using the contextual() function private val module = SerializersModule { contextual(DateAsLongSerializer) } -``` - -Next we create an instance of the [Json] format with this module using the -[Json {}][Json()] builder function and the [serializersModule][JsonBuilder.serializersModule] property. -> Details on custom JSON configurations can be found in -> the [JSON configuration](json.md#json-configuration) section. - -```kotlin +// Creates an instance of Json with the custom SerializersModule val format = Json { serializersModule = module } -``` -Now we can serialize our data with this `format`. - -```kotlin fun main() { val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) println(format.encodeToString(data)) + // {"name":"Kotlin","stableReleaseDate":1455494400000} } +//sampleEnd ``` +{kotlin-runnable="true"} -> You can get the full code [here](../../guide/example/example-serializer-21.kt). + + + -### Contextual serialization and generic classes +### Serialize generic classes contextually -In the previous section we saw that we can register serializer instance in the module for a class we want to serialize contextually. -We also know that [serializers for generic classes have constructor parameters](#custom-serializers-for-a-generic-type) — type arguments serializers. -It means that we can't use one serializer instance for a class if this class is generic: +To serialize generic classes contextually, you can register a function in the `SerializersModule` +that dynamically provides the appropriate serializer based on the generic type used at runtime. + +You cannot use a single serializer instance for a generic class because [different type arguments require different serializers](third-party-classes.md#specify-a-custom-serializer-for-generic-types). +For example, the following approach would only work for `Box`, but not for other types like `Box`: ```kotlin val incorrectModule = SerializersModule { - // Can serialize only Box, but not Box or others + // This only works for Box, but not for Box or other types contextual(BoxSerializer(Int.serializer())) } ``` -For cases when one want to serialize contextually a generic class, it is possible to register provider in the module: +Instead, you can use a function that provides the correct serializer for any type argument. +The following example demonstrates how to register a function that dynamically supplies the appropriate serializer for generic types like `Box`: ```kotlin val correctModule = SerializersModule { - // args[0] contains Int.serializer() or String.serializer(), depending on the usage - contextual(Box::class) { args -> BoxSerializer(args[0]) } + // args[0] dynamically provides the appropriate serializer for the type argument + // For example, Int.serializer() or String.serializer() + contextual(Box::class) { args -> BoxSerializer(args[0]) } } ``` -> Additional details on serialization modules are given in -> the [Merging library serializers modules](polymorphism.md#merging-library-serializers-modules) section of -> the [Polymorphism](polymorphism.md) chapter. - -## Deriving external serializer for another Kotlin class (experimental) - -If a 3rd-party class to be serialized is a Kotlin class with a properties-only primary constructor, a kind of -class which could have been made `@Serializable`, then you can generate an _external_ serializer for it -using the [Serializer] annotation on an object with the [`forClass`][Serializer.forClass] property. - -```kotlin -// NOT @Serializable -class Project(val name: String, val language: String) - -@Serializer(forClass = Project::class) -object ProjectSerializer -``` - -You must bind this serializer to a class using one of the approaches explained in this chapter. We'll -follow the [Passing a serializer manually](#passing-a-serializer-manually) approach for this example. - -```kotlin -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(ProjectSerializer, data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-22.kt). - -This gets all the `Project` properties serialized: - -```text -{"name":"kotlinx.serialization","language":"Kotlin"} -``` - - - -### External serialization uses properties - -As we saw earlier, the regular `@Serializable` annotation creates a serializer so that -[Backing fields are serialized](basic-serialization.md#backing-fields-are-serialized). _External_ serialization using -`Serializer(forClass = ...)` has no access to backing fields and works differently. -It serializes only _accessible_ properties that have setters or are part of the primary constructor. -The following example shows this. - -```kotlin -// NOT @Serializable, will use external serializer -class Project( - // val in a primary constructor -- serialized - val name: String -) { - var stars: Int = 0 // property with getter & setter -- serialized - - val path: String // getter only -- not serialized - get() = "kotlin/$name" - - private var locked: Boolean = false // private, not accessible -- not serialized -} - -@Serializer(forClass = Project::class) -object ProjectSerializer - -fun main() { - val data = Project("kotlinx.serialization").apply { stars = 9000 } - println(Json.encodeToString(ProjectSerializer, data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-23.kt). - -The output is shown below. - -```text -{"name":"kotlinx.serialization","stars":9000} -``` - - - - -### Base64 +> If you have multiple `SerializersModule` instances, such as those handling both generic and non-generic classes, +> you can combine them using the `plus` operator. +> For more details on merging modules, see the [Merging library serializers modules](serialization-polymorphism.md#merge-multiple-serializermodule-instances) section. +> +{type="tip"} -To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default -implementation of Kotlin's Base64 encoder. Note that some serializers use different RFCs for Base64 encoding by default. -For example, Jackson uses a variant of [Base64 Mime](https://datatracker.ietf.org/doc/html/rfc2045). The same result in -kotlinx.serialization can be achieved with Base64.Mime encoder. -[Kotlin's documentation for Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) lists -other available encoders. +## What's next -```kotlin -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.descriptors.* -import kotlin.io.encoding.* - -@OptIn(ExperimentalEncodingApi::class) -object ByteArrayAsBase64Serializer : KSerializer { - private val base64 = Base64.Default - - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor( - "ByteArrayAsBase64Serializer", - PrimitiveKind.STRING - ) - - override fun serialize(encoder: Encoder, value: ByteArray) { - val base64Encoded = base64.encode(value) - encoder.encodeString(base64Encoded) - } - - override fun deserialize(decoder: Decoder): ByteArray { - val base64Decoded = decoder.decodeString() - return base64.decode(base64Decoded) - } -} -``` - -For more details on how to create your own custom serializer, you can -see [custom serializers](serializers.md#custom-serializers). - -Then we can use it like this: - -```kotlin -@Serializable -data class Value( - @Serializable(with = ByteArrayAsBase64Serializer::class) - val base64Input: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - other as Value - return base64Input.contentEquals(other.base64Input) - } - - override fun hashCode(): Int { - return base64Input.contentHashCode() - } -} - -fun main() { - val string = "foo string" - val value = Value(string.toByteArray()) - val encoded = Json.encodeToString(value) - println(encoded) - val decoded = Json.decodeFromString(encoded) - println(decoded.base64Input.decodeToString()) -} -``` - -> You can get the full code [here](../../guide/example/example-json-16.kt) - -```text -{"base64Input":"Zm9vIHN0cmluZw=="} -foo string -``` - -Notice the serializer we wrote is not dependent on `Json` format, therefore, it can be used in any format. - -For projects that use this serializer in many places, to avoid specifying the serializer every time, it is possible -to [specify a serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias). -For example: -````kotlin -typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray -```` +* The [Json transformations](json.md#json-transformations) section of the [Json](json.md) chapter provides examples + of serializers that utilize JSON-specific features. - \ No newline at end of file +* A format implementation can have a format-specific representation for a type as explained + in the [Format-specific types](formats.md#format-specific-types) section of + the [Alternative and custom formats (experimental)](formats.md) chapter. \ No newline at end of file diff --git a/docs/topics/formats.md b/docs/topics/formats.md index ca5fba0e4d..18cacdd874 100644 --- a/docs/topics/formats.md +++ b/docs/topics/formats.md @@ -221,6 +221,7 @@ BF # map(*) FF # primitive(*) ``` + ## ProtoBuf (experimental) [Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally diff --git a/docs/topics/third-party-classes.md b/docs/topics/third-party-classes.md index e6e70fc984..dc15a06803 100644 --- a/docs/topics/third-party-classes.md +++ b/docs/topics/third-party-classes.md @@ -263,4 +263,94 @@ fun main() { ``` --> + + +## Create an external serializer for third-party Kotlin classes (experimental) + +When working with third-party Kotlin classes that have a properties-only primary constructor, +you can create an external serializer to handle their serialization. +This can be useful when you can't annotate the class with `@Serializable` because you don’t control its source code. + +To do this, use the `@Serializer` annotation with the [`forClass`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/for-class.html) property to specify the class it handles: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +// This class cannot be annotated with @Serializable +class Project(val name: String, val language: String) + +// Defines an external serializer for the Project class +@Serializer(forClass = Project::class) +object ProjectSerializer + +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + // Serializes the Project object using the external serializer + println(Json.encodeToString(ProjectSerializer, data)) + // {"name":"kotlinx.serialization","language":"Kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +When using an external serializer with the `@Serializer` annotation, only accessible properties are serialized. +This includes properties that are part of the primary constructor and those that have both getters and setters. +However, properties with only getters or private properties without accessors (getters and setters) are not serialized: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +// This class cannot be annotated with @Serializable +class Project( + // Part of the primary constructor -- serialized + val name: String +) { + // Property with getter and setter -- serialized + var stars: Int = 0 + + // Getter only -- not serialized + val path: String + get() = "kotlin/$name" + + // Private property -- not serialized + private var locked: Boolean = false +} + +// Defines an external serializer for the Project class +@Serializer(forClass = Project::class) +object ProjectSerializer + +fun main() { + val data = Project("kotlinx.serialization").apply { stars = 9000 } + // Serializes only accessible properties (name, stars) + println(Json.encodeToString(ProjectSerializer, data)) + // {"name":"kotlinx.serialization","stars":9000} +} +``` +{kotlin-runnable="true"} + + + + + \ No newline at end of file From eada1829f1ee504d1c23373ad996f3ed130aac31 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Tue, 15 Oct 2024 16:34:05 +0200 Subject: [PATCH 11/14] preparing for writerside migration --- docs/images/icons/icon-1-done.svg | 4 - docs/images/icons/icon-1.svg | 4 - docs/images/icons/icon-2-done.svg | 4 - docs/images/icons/icon-2-todo.svg | 4 - docs/images/icons/icon-2.svg | 4 - docs/images/icons/icon-3-done.svg | 4 - docs/images/icons/icon-3-todo.svg | 4 - docs/images/icons/icon-3.svg | 4 - docs/images/icons/icon-4-done.svg | 4 - docs/images/icons/icon-4-todo.svg | 4 - docs/images/icons/icon-4.svg | 4 - docs/images/icons/icon-5-done.svg | 4 - docs/images/icons/icon-5-todo.svg | 4 - docs/images/icons/icon-5.svg | 4 - docs/images/icons/icon-6-done.svg | 4 - docs/images/icons/icon-6-todo.svg | 4 - docs/images/icons/icon-6.svg | 4 - docs/images/icons/icon-7-todo.svg | 4 - docs/images/icons/icon-7.svg | 4 - .../alternative-serialization-formats.md | 251 +-- docs/topics/basic-serialization.md | 701 -------- docs/topics/builtin-classes.md | 464 ----- docs/topics/configure-json-serialization.md | 17 +- docs/topics/create-custom-serializers.md | 2 +- docs/topics/formats.md | 1524 ----------------- docs/topics/json.md | 35 - docs/topics/polymorphism.md | 1040 ----------- .../serialization-customization-options.md | 4 +- .../serialization-get-started-create.md | 170 -- .../serialization-get-started-overview.md | 19 - .../serialization-get-started-serialize.md | 49 - docs/topics/serialization-get-started.md | 75 +- docs/topics/serialization-guide.md | 146 -- .../serialization-serialize-builtin-types.md | 498 +++++- docs/topics/serialization-transform-json.md | 103 +- docs/topics/serialization.md | 75 +- docs/topics/serializers.md | 1258 -------------- docs/topics/value-classes.md | 4 +- 38 files changed, 744 insertions(+), 5767 deletions(-) delete mode 100644 docs/images/icons/icon-1-done.svg delete mode 100644 docs/images/icons/icon-1.svg delete mode 100644 docs/images/icons/icon-2-done.svg delete mode 100644 docs/images/icons/icon-2-todo.svg delete mode 100644 docs/images/icons/icon-2.svg delete mode 100644 docs/images/icons/icon-3-done.svg delete mode 100644 docs/images/icons/icon-3-todo.svg delete mode 100644 docs/images/icons/icon-3.svg delete mode 100644 docs/images/icons/icon-4-done.svg delete mode 100644 docs/images/icons/icon-4-todo.svg delete mode 100644 docs/images/icons/icon-4.svg delete mode 100644 docs/images/icons/icon-5-done.svg delete mode 100644 docs/images/icons/icon-5-todo.svg delete mode 100644 docs/images/icons/icon-5.svg delete mode 100644 docs/images/icons/icon-6-done.svg delete mode 100644 docs/images/icons/icon-6-todo.svg delete mode 100644 docs/images/icons/icon-6.svg delete mode 100644 docs/images/icons/icon-7-todo.svg delete mode 100644 docs/images/icons/icon-7.svg delete mode 100644 docs/topics/basic-serialization.md delete mode 100644 docs/topics/builtin-classes.md delete mode 100644 docs/topics/formats.md delete mode 100644 docs/topics/json.md delete mode 100644 docs/topics/polymorphism.md delete mode 100644 docs/topics/serialization-get-started-create.md delete mode 100644 docs/topics/serialization-get-started-overview.md delete mode 100644 docs/topics/serialization-get-started-serialize.md delete mode 100644 docs/topics/serialization-guide.md delete mode 100644 docs/topics/serializers.md diff --git a/docs/images/icons/icon-1-done.svg b/docs/images/icons/icon-1-done.svg deleted file mode 100644 index ef2d7c50a1..0000000000 --- a/docs/images/icons/icon-1-done.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-1.svg b/docs/images/icons/icon-1.svg deleted file mode 100644 index 5316ebc265..0000000000 --- a/docs/images/icons/icon-1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-2-done.svg b/docs/images/icons/icon-2-done.svg deleted file mode 100644 index b77d5c47c9..0000000000 --- a/docs/images/icons/icon-2-done.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-2-todo.svg b/docs/images/icons/icon-2-todo.svg deleted file mode 100644 index de373b82c3..0000000000 --- a/docs/images/icons/icon-2-todo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-2.svg b/docs/images/icons/icon-2.svg deleted file mode 100644 index ad2c4ec448..0000000000 --- a/docs/images/icons/icon-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-3-done.svg b/docs/images/icons/icon-3-done.svg deleted file mode 100644 index d09c6e8b93..0000000000 --- a/docs/images/icons/icon-3-done.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-3-todo.svg b/docs/images/icons/icon-3-todo.svg deleted file mode 100644 index 8c715a0351..0000000000 --- a/docs/images/icons/icon-3-todo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-3.svg b/docs/images/icons/icon-3.svg deleted file mode 100644 index e934f21a7f..0000000000 --- a/docs/images/icons/icon-3.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-4-done.svg b/docs/images/icons/icon-4-done.svg deleted file mode 100644 index ea096e0e3a..0000000000 --- a/docs/images/icons/icon-4-done.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-4-todo.svg b/docs/images/icons/icon-4-todo.svg deleted file mode 100644 index 91a1fd8c65..0000000000 --- a/docs/images/icons/icon-4-todo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-4.svg b/docs/images/icons/icon-4.svg deleted file mode 100644 index 8558746c2d..0000000000 --- a/docs/images/icons/icon-4.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-5-done.svg b/docs/images/icons/icon-5-done.svg deleted file mode 100644 index ce264804a3..0000000000 --- a/docs/images/icons/icon-5-done.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-5-todo.svg b/docs/images/icons/icon-5-todo.svg deleted file mode 100644 index 47be40b199..0000000000 --- a/docs/images/icons/icon-5-todo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-5.svg b/docs/images/icons/icon-5.svg deleted file mode 100644 index ced6626703..0000000000 --- a/docs/images/icons/icon-5.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-6-done.svg b/docs/images/icons/icon-6-done.svg deleted file mode 100644 index 513defa5b2..0000000000 --- a/docs/images/icons/icon-6-done.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-6-todo.svg b/docs/images/icons/icon-6-todo.svg deleted file mode 100644 index 96855e9235..0000000000 --- a/docs/images/icons/icon-6-todo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-6.svg b/docs/images/icons/icon-6.svg deleted file mode 100644 index 39e537598a..0000000000 --- a/docs/images/icons/icon-6.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-7-todo.svg b/docs/images/icons/icon-7-todo.svg deleted file mode 100644 index f16c0c8ec2..0000000000 --- a/docs/images/icons/icon-7-todo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/images/icons/icon-7.svg b/docs/images/icons/icon-7.svg deleted file mode 100644 index bfbb8a07c2..0000000000 --- a/docs/images/icons/icon-7.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/topics/alternative-serialization-formats.md b/docs/topics/alternative-serialization-formats.md index 128b8e7e1b..a32582b5dd 100644 --- a/docs/topics/alternative-serialization-formats.md +++ b/docs/topics/alternative-serialization-formats.md @@ -1,9 +1,12 @@ -[//]: # (title: Alternative and custom serialization formats) +[//]: # (title: Alternative and custom serialization formats (experimental)) -Unlike JSON, which is -stable, these are currently experimental features of Kotlin Serialization. +While JSON is the only stable format in Kotlin Serialization, +experimental support is available for several alternative formats, +including binary formats like CBOR and ProtoBuf, as well as custom formats. +These experimental formats provide additional flexibility and efficiency for performance-critical applications, +though they may be subject to change in future releases. ## CBOR (experimental) @@ -471,7 +474,7 @@ for small signed integers. For example, it encodes the value of `-2` in one byte * The [`FIXED`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/) option uses fixed-width encoding (`fixedXX`), which always uses a fixed number of bytes. For example, it encodes the value of `3` as four bytes `03 00 00 00`. -> `uintXX` and `sfixedXX` protocol buffer types are not supported. +> `uintXX` and `sfixedXX` Protocol Buffer types are not supported. > {type="note"} @@ -555,7 +558,7 @@ According to the ProtoBuf standard, packed fields are supported only for primiti As described in the [format specification](https://developers.google.com/protocol-buffers/docs/encoding#packed), the ProtoBuf parser automatically handles lists in either packed or repeated format, regardless of the annotation. -### The oneof field (experimental) +### The oneof field ProtoBuf serialization in Kotlin supports [oneof](https://protobuf.dev/programming-guides/proto2/#oneof) fields using Kotlin's [polymorphism](serialization-polymorphism.md) functionality. @@ -666,7 +669,7 @@ However, it doesn't enforce exclusivity between `homeNumber` and `workNumber`. If both fields have values or are both `null`, the serialized output may not comply with the original schema, which could cause compatibility issues when parsed by other ProtoBuf tools. -### Generate a ProtoBuf schema (experimental) +### Generate a ProtoBuf schema Typically, working with ProtoBuf involves using a `.proto` file and a code generator to create code for serialization and deserialization. However, with Kotlin Serialization, you can use Kotlin classes annotated with `@Serializable` as the source for the schema, making `.proto` files optional. @@ -788,6 +791,99 @@ owner.name = kotlin +## Encode and decode Base64 formats (experimental) + +[Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) encoding transforms binary data into a text-based format, +which can be easily transmitted or stored in formats like JSON or XML. +You can apply a custom Base64 serializer to any format that supports string-based encoding, +allowing for flexibility in how binary data is handled across various formats. + +To encode and decode Base64 formats, create a serializer that controls how data is transformed into and from Base64: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.descriptors.* +import kotlin.io.encoding.* + +@OptIn(ExperimentalEncodingApi::class) +// Custom serializer for converting ByteArray to Base64 format and back +object ByteArrayAsBase64Serializer : KSerializer { + private val base64 = Base64.Default + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor( + "ByteArrayAsBase64Serializer", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: ByteArray) { + val base64Encoded = base64.encode(value) + encoder.encodeString(base64Encoded) + } + + override fun deserialize(decoder: Decoder): ByteArray { + val base64Decoded = decoder.decodeString() + return base64.decode(base64Decoded) + } +} + +@Serializable +data class Value( + @Serializable(with = ByteArrayAsBase64Serializer::class) + val base64Input: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as Value + return base64Input.contentEquals(other.base64Input) + } + + override fun hashCode(): Int { + return base64Input.contentHashCode() + } +} + +fun main() { + val string = "foo string" + val value = Value(string.toByteArray()) + + // Encodes the data class to a Base64 string + val encoded = Json.encodeToString(value) + println(encoded) + // {"base64Input":"Zm9vIHN0cmluZw=="} + + // Decodes the Base64 string back to its original form + val decoded = Json.decodeFromString(encoded) + println(decoded.base64Input.decodeToString()) + // foo string +} +``` +{kotlin-runnable="true"} + + + + + + + +> For projects frequently using a Base64 serializer, you can [apply it globally](third-party-classes.md#specify-custom-serializers-globally-using-typealias) using the `typealias` keyword: +> +> ```kotlin +> typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray +> ``` +> +{type="tip"} + ## Create custom formats (experimental) To implement a custom format in Kotlin Serialization, @@ -867,7 +963,7 @@ fun main() { This example demonstrates how to create a custom `ListEncoder` to transform a Kotlin object into a list of its individual values, maintaining the order they are defined in the class. - + + + + + + + - -### Base64 - -To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default -implementation of Kotlin's Base64 encoder. Note that some serializers use different RFCs for Base64 encoding by default. -For example, Jackson uses a variant of [Base64 Mime](https://datatracker.ietf.org/doc/html/rfc2045). The same result in -kotlinx.serialization can be achieved with Base64.Mime encoder. -[Kotlin's documentation for Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) lists -other available encoders. - -```kotlin -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.descriptors.* -import kotlin.io.encoding.* - -@OptIn(ExperimentalEncodingApi::class) -object ByteArrayAsBase64Serializer : KSerializer { - private val base64 = Base64.Default - - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor( - "ByteArrayAsBase64Serializer", - PrimitiveKind.STRING - ) - - override fun serialize(encoder: Encoder, value: ByteArray) { - val base64Encoded = base64.encode(value) - encoder.encodeString(base64Encoded) - } - - override fun deserialize(decoder: Decoder): ByteArray { - val base64Decoded = decoder.decodeString() - return base64.decode(base64Decoded) - } -} -``` - -For more details on how to create your own custom serializer, you can -see [custom serializers](serializers.md#custom-serializers). - -Then we can use it like this: - -```kotlin -@Serializable -data class Value( - @Serializable(with = ByteArrayAsBase64Serializer::class) - val base64Input: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - other as Value - return base64Input.contentEquals(other.base64Input) - } - - override fun hashCode(): Int { - return base64Input.contentHashCode() - } -} - -fun main() { - val string = "foo string" - val value = Value(string.toByteArray()) - val encoded = Json.encodeToString(value) - println(encoded) - val decoded = Json.decodeFromString(encoded) - println(decoded.base64Input.decodeToString()) -} -``` - - - - - -Notice the serializer we wrote is not dependent on `Json` format, therefore, it can be used in any format. - -For projects that use this serializer in many places, to avoid specifying the serializer every time, it is possible -to [specify a serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias). -For example: - -````kotlin -typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray -```` - - diff --git a/docs/topics/basic-serialization.md b/docs/topics/basic-serialization.md deleted file mode 100644 index c6931c607c..0000000000 --- a/docs/topics/basic-serialization.md +++ /dev/null @@ -1,701 +0,0 @@ - - -# Basic Serialization - -This is the first chapter of the [Kotlin Serialization Guide](serialization-guide.md). -This chapter shows the basic use of Kotlin Serialization and explains its core concepts. - -**Table of contents** - - - -* [Basics](#basics) - * [JSON encoding](#json-encoding) - * [JSON decoding](#json-decoding) -* [Serializable classes](#serializable-classes) - * [Backing fields are serialized](#backing-fields-are-serialized) - * [Constructor properties requirement](#constructor-properties-requirement) - * [Data validation](#data-validation) - * [Optional properties](#optional-properties) - * [Optional property initializer call](#optional-property-initializer-call) - * [Required properties](#required-properties) - * [Transient properties](#transient-properties) - * [Defaults are not encoded by default](#defaults-are-not-encoded-by-default) - * [Nullable properties](#nullable-properties) - * [Type safety is enforced](#type-safety-is-enforced) - * [Referenced objects](#referenced-objects) - * [No compression of repeated references](#no-compression-of-repeated-references) - * [Generic classes](#generic-classes) - * [Serial field names](#serial-field-names) - - - -## Basics - -To convert an object tree to a string or to a sequence of bytes, it must come -through two mutually intertwined processes. In the first step, an object is _serialized_—it -is converted into a serial sequence of its constituting primitive values. This process is common for all -data formats and its result depends on the object being serialized. A _serializer_ controls this process. -The second step is called _encoding_—it is the conversion of the corresponding sequence of primitives into -the output format representation. An _encoder_ controls this process. Whenever the distinction is not important, -both the terms of encoding and serialization are used interchangeably. - -``` -+---------+ Serialization +------------+ Encoding +---------------+ -| Objects | --------------> | Primitives | ---------> | Output format | -+---------+ +------------+ +---------------+ -``` - -The reverse process starts with parsing of the input format and _decoding_ of primitive values, -followed by _deserialization_ of the resulting stream into objects. We'll see details of this process later. - -For now, we start with [JSON](https://json.org) encoding. - - - -### JSON encoding - -The whole process of converting data into a specific format is called _encoding_. For JSON we encode data -using the [Json.encodeToString][kotlinx.serialization.encodeToString] extension function. It serializes -the object that is passed as its parameter under the hood and encodes it to a JSON string. - -Let's start with a class describing a project and try to get its JSON representation. - -```kotlin -class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-basic-01.kt). - -When we run this code we get the exception. - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found. -Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. -``` - - - -Serializable classes have to be explicitly marked. Kotlin Serialization does not use reflection, -so you cannot accidentally deserialize a class which was not supposed to be serializable. We fix it by -adding the [`@Serializable`][Serializable] annotation. - -```kotlin -@Serializable -class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-basic-02.kt). - -The `@Serializable` annotation instructs the Kotlin Serialization plugin to automatically generate and hook -up a _serializer_ for this class. Now the output of the example is the corresponding JSON. - -```text -{"name":"kotlinx.serialization","language":"Kotlin"} -``` - - - -> There is a whole chapter about the [Serializers](serializers.md). For now, it is enough to know -> that they are automatically generated by the Kotlin Serialization plugin. - -### JSON decoding - -The reverse process is called _decoding_. To decode a JSON string into an object, we'll -use the [Json.decodeFromString][kotlinx.serialization.decodeFromString] extension function. -To specify which type we want to get as a result, we provide a type parameter to this function. - -As we'll see later, serialization works with different kinds of classes. -Here we are marking our `Project` class as a `data class`, not because it is required, but because -we want to print its contents to verify how it decodes. - -```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-basic-03.kt). - -Running this code we get back the object. - -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -## Serializable classes - -This section goes into more details on how different `@Serializable` classes are handled. - - - -### Backing fields are serialized - -Only a class's properties with backing fields are serialized, so properties with a getter/setter that don't -have a backing field and delegated properties are not serialized, as the following example shows. - -```kotlin -@Serializable -class Project( - // name is a property with backing field -- serialized - var name: String -) { - var stars: Int = 0 // property with a backing field -- serialized - - val path: String // getter only, no backing field -- not serialized - get() = "kotlin/$name" - - var id by ::name // delegated property -- not serialized -} - -fun main() { - val data = Project("kotlinx.serialization").apply { stars = 9000 } - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-01.kt). - -We can clearly see that only the `name` and `stars` properties are present in the JSON output. - -```text -{"name":"kotlinx.serialization","stars":9000} -``` - - - -### Constructor properties requirement - -If we want to define the `Project` class so that it takes a path string, and then -deconstructs it into the corresponding properties, we might be tempted to write something like the code below. - -```kotlin -@Serializable -class Project(path: String) { - val owner: String = path.substringBefore('/') - val name: String = path.substringAfter('/') -} -``` - - - -This class does not compile because the `@Serializable` annotation requires that all parameters of the class's primary -constructor be properties. A simple workaround is to define a private primary constructor with the class's -properties, and turn the constructor we wanted into the secondary one. - -```kotlin -@Serializable -class Project private constructor(val owner: String, val name: String) { - constructor(path: String) : this( - owner = path.substringBefore('/'), - name = path.substringAfter('/') - ) - - val path: String - get() = "$owner/$name" -} -``` - -Serialization works with a private primary constructor, and still serializes only backing fields. - -```kotlin -fun main() { - println(Json.encodeToString(Project("kotlin/kotlinx.serialization"))) -} -``` - -> You can get the full code [here](../guide/example/example-classes-02.kt). - -This example produces the expected output. - -```text -{"owner":"kotlin","name":"kotlinx.serialization"} -``` - - - -### Data validation - -Another case where you might want to introduce a primary constructor parameter without a property is when you -want to validate its value before storing it to a property. To make it serializable you shall replace it -with a property in the primary constructor, and move the validation to an `init { ... }` block. - -```kotlin -@Serializable -class Project(val name: String) { - init { - require(name.isNotEmpty()) { "name cannot be empty" } - } -} -``` - -A deserialization process works like a regular constructor in Kotlin and calls all `init` blocks, ensuring that you -cannot get an invalid class as a result of deserialization. Let's try it. - -```kotlin -fun main() { - val data = Json.decodeFromString(""" - {"name":""} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-03.kt). - -Running this code produces the exception: - -```text -Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty -``` - - - -### Optional properties - -An object can be deserialized only when all its properties are present in the input. -For example, run the following code. - -```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-04.kt). - -It produces the exception: - -```text -Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $ -``` - - - -This problem can be fixed by adding a default value to the property, which automatically makes it optional -for serialization. - -```kotlin -@Serializable -data class Project(val name: String, val language: String = "Kotlin") - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-05.kt). - -It produces the following output with the default value for the `language` property. - -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -### Optional property initializer call - -When an optional property is present in the input, the corresponding initializer for this -property is not even called. This is a feature designed for performance, so be careful not -to rely on side effects in initializers. Consider the example below. - -```kotlin -fun computeLanguage(): String { - println("Computing") - return "Kotlin" -} - -@Serializable -data class Project(val name: String, val language: String = computeLanguage()) - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-06.kt). - -Since the `language` property was specified in the input, we don't see the "Computing" string printed -in the output. - -```text -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -### Required properties - -A property with a default value can be required in a serial format with the [`@Required`][Required] annotation. -Let us change the previous example by marking the `language` property as `@Required`. - -```kotlin -@Serializable -data class Project(val name: String, @Required val language: String = "Kotlin") - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization"} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-07.kt). - -We get the following exception. - -```text -Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $ -``` - - - -### Transient properties - -A property can be excluded from serialization by marking it with the [`@Transient`][Transient] annotation -(don't confuse it with [kotlin.jvm.Transient]). Transient properties must have a default value. - -```kotlin -@Serializable -data class Project(val name: String, @Transient val language: String = "Kotlin") - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-08.kt). - -Attempts to explicitly specify its value in the serial format, even if the specified -value is equal to the default one, produces the following exception. - -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name -Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys. -``` - - - -> The 'ignoreUnknownKeys' feature is explained in the [Ignoring Unknown Keys section](json.md#ignoring-unknown-keys) section. - -### Defaults are not encoded by default - -Default values are not encoded by default in JSON. This behavior is motivated by the fact that in most real-life scenarios -such configuration reduces visual clutter, and saves the amount of data being serialized. - -```kotlin -@Serializable -data class Project(val name: String, val language: String = "Kotlin") - -fun main() { - val data = Project("kotlinx.serialization") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-09.kt). - -It produces the following output, which does not have the `language` property because its value is equal to the default one. - -```text -{"name":"kotlinx.serialization"} -``` - - - -See JSON's [Encoding defaults](json.md#encoding-defaults) section on how this behavior can be configured for JSON. -Additionally, this behavior can be controlled without taking format settings into account. -For that purposes, [EncodeDefault] annotation can be used: - -```kotlin -@Serializable -data class Project( - val name: String, - @EncodeDefault val language: String = "Kotlin" -) -``` - -This annotation instructs the framework to always serialize property, regardless of its value or format settings. -It's also possible to tweak it into the opposite behavior using [EncodeDefault.Mode] parameter: - -```kotlin - -@Serializable -data class User( - val name: String, - @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() -) - -fun main() { - val userA = User("Alice", listOf(Project("kotlinx.serialization"))) - val userB = User("Bob") - println(Json.encodeToString(userA)) - println(Json.encodeToString(userB)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-10.kt). - -As you can see, `language` property is preserved and `projects` is omitted: - -```text -{"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} -{"name":"Bob"} -``` - - - -### Nullable properties - -Nullable properties are natively supported by Kotlin Serialization. - -```kotlin -@Serializable -class Project(val name: String, val renamedTo: String? = null) - -fun main() { - val data = Project("kotlinx.serialization") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-11.kt). - -This example does not encode `null` in JSON because [Defaults are not encoded](#defaults-are-not-encoded). - -```text -{"name":"kotlinx.serialization"} -``` - - - -### Type safety is enforced - -Kotlin Serialization strongly enforces the type safety of the Kotlin programming language. -In particular, let us try to decode a `null` value from a JSON object into a non-nullable Kotlin property `language`. - -```kotlin -@Serializable -data class Project(val name: String, val language: String = "Kotlin") - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":null} - """) - println(data) -} -``` - -> You can get the full code [here](../guide/example/example-classes-12.kt). - -Even though the `language` property has a default value, it is still an error to attempt to assign -the `null` value to it. - -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language -Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value. -``` - - - -> It might be desired, when decoding 3rd-party JSONs, to coerce `null` to a default value. -> The corresponding feature is explained in the [Coercing input values](json.md#coercing-input-values) section. - -### Referenced objects - -Serializable classes can reference other classes in their serializable properties. -The referenced classes must be also marked as `@Serializable`. - -```kotlin -@Serializable -class Project(val name: String, val owner: User) - -@Serializable -class User(val name: String) - -fun main() { - val owner = User("kotlin") - val data = Project("kotlinx.serialization", owner) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-13.kt). - -When encoded to JSON it results in a nested JSON object. - -```text -{"name":"kotlinx.serialization","owner":{"name":"kotlin"}} -``` - -> References to non-serializable classes can be marked as [Transient properties](#transient-properties), or a -> custom serializer can be provided for them as shown in the [Serializers](serializers.md) chapter. - - - -### No compression of repeated references - -Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction -of arbitrary object graphs with repeated object references. For example, let us try to serialize an object -that references the same `owner` instance twice. - -```kotlin -@Serializable -class Project(val name: String, val owner: User, val maintainer: User) - -@Serializable -class User(val name: String) - -fun main() { - val owner = User("kotlin") - val data = Project("kotlinx.serialization", owner, owner) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-14.kt). - -We simply get the `owner` value encoded twice. - -```text -{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} -``` - -> Attempt to serialize a circular structure will result in stack overflow. -> You can use the [Transient properties](#transient-properties) to exclude some references from serialization. - - - -### Generic classes - -Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at -compile-time. For example, consider a generic serializable class `Box`. - -```kotlin -@Serializable -class Box(val contents: T) -``` - -The `Box` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`. - - - -```kotlin -@Serializable -class Data( - val a: Box, - val b: Box -) - -fun main() { - val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin"))) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-15.kt). - -The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for `Box`. - -```text -{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}} -``` - - - -If the actual generic type is not serializable a compile-time error will be produced. - -### Serial field names - -The names of the properties used in encoded representation, JSON in our examples, are the same as -their names in the source code by default. The name that is used for serialization is called a _serial name_, and -can be changed using the [`@SerialName`][SerialName] annotation. For example, we can have a `language` property in -the source with an abbreviated serial name. - -```kotlin -@Serializable -class Project(val name: String, @SerialName("lang") val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../guide/example/example-classes-16.kt). - -Now we see that an abbreviated name `lang` is used in the JSON output. - -```text -{"name":"kotlinx.serialization","lang":"Kotlin"} -``` - - - ---- - -The next chapter covers [Builtin classes](builtin-classes.md). - - -[kotlin.jvm.Transient]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-transient/ - - - - -[kotlinx.serialization.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html -[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html -[kotlinx.serialization.decodeFromString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html -[Required]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/index.html -[Transient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html -[EncodeDefault]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/index.html -[EncodeDefault.Mode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/index.html -[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html - - - - diff --git a/docs/topics/builtin-classes.md b/docs/topics/builtin-classes.md deleted file mode 100644 index 00d27882fa..0000000000 --- a/docs/topics/builtin-classes.md +++ /dev/null @@ -1,464 +0,0 @@ - - -# Builtin classes - -This is the second chapter of the [Kotlin Serialization Guide](serialization-guide.md). -In addition to all the primitive types and strings, serialization for some classes from the Kotlin standard library, -including the standard collections, is built into Kotlin Serialization. This chapter explains the details. - -**Table of contents** - - - -* [Primitives](#primitives) - * [Numbers](#numbers) - * [Long numbers](#long-numbers) - * [Long numbers as strings](#long-numbers-as-strings) - * [Enum classes](#enum-classes) - * [Serial names of enum entries](#serial-names-of-enum-entries) -* [Composites](#composites) - * [Pair and triple](#pair-and-triple) - * [Lists](#lists) - * [Sets and other collections](#sets-and-other-collections) - * [Deserializing collections](#deserializing-collections) - * [Maps](#maps) - * [Unit and singleton objects](#unit-and-singleton-objects) - * [Duration](#duration) -* [Nothing](#nothing) - - - - - -## Primitives - -Kotlin Serialization has the following ten primitives: -`Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `Char`, `String`, and enums. -The other types in Kotlin Serialization are _composite_—composed of those primitive values. - -### Numbers - -All types of integer and floating-point Kotlin numbers can be serialized. - - - -```kotlin -@Serializable -class Data( - val answer: Int, - val pi: Double -) - -fun main() { - val data = Data(42, PI) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-01.kt). - -Their natural representation in JSON is used. - -```text -{"answer":42,"pi":3.141592653589793} -``` - - - - -### Long numbers - -Long integers are serializable, too. - -```kotlin -@Serializable -class Data(val signature: Long) - -fun main() { - val data = Data(0x1CAFE2FEED0BABE0) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-02.kt). - -By default they are serialized to JSON as numbers. - -```text -{"signature":2067120338512882656} -``` - - - -### Long numbers as strings - -The JSON output from the previous example will get decoded normally by Kotlin Serialization running on Kotlin/JS. -However, if we try to parse this JSON by native JavaScript methods, we get this truncated result. - -``` -JSON.parse("{\"signature\":2067120338512882656}") -▶ {signature: 2067120338512882700} -``` - -The full range of a Kotlin Long does not fit in the JavaScript number, so its precision gets lost in JavaScript. -A common workaround is to represent long numbers with full precision using the JSON string type. -This approach is optionally supported by Kotlin Serialization with [LongAsStringSerializer], which -can be specified for a given Long property using the [`@Serializable`][Serializable] annotation: - - - -```kotlin -@Serializable -class Data( - @Serializable(with=LongAsStringSerializer::class) - val signature: Long -) - -fun main() { - val data = Data(0x1CAFE2FEED0BABE0) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-03.kt). - -This JSON gets parsed natively by JavaScript without loss of precision. - -```text -{"signature":"2067120338512882656"} -``` - -> The section on [Specifying serializers for a file](serializers.md#specifying-serializers-for-a-file) explains how a -> serializer like `LongAsStringSerializer` can be specified for all properties in a file. - - - -### Enum classes - -All enum classes are serializable out of the box without having to mark them `@Serializable`, -as the following example shows. - -```kotlin -// The @Serializable annotation is not needed for enum classes -enum class Status { SUPPORTED } - -@Serializable -class Project(val name: String, val status: Status) - -fun main() { - val data = Project("kotlinx.serialization", Status.SUPPORTED) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-04.kt). - -In JSON an enum gets encoded as a string. - -```text -{"name":"kotlinx.serialization","status":"SUPPORTED"} -``` - -> Note: On Kotlin/JS and Kotlin/Native, `@Serializable` annotation is needed for enum class if you want to use it as a root object — i.e. use `encodeToString(Status.SUPPORTED)`. - - - -### Serial names of enum entries - -Serial names of enum entries can be customized with the [SerialName] annotation just like -it was shown for properties in the [Serial field names](basic-serialization.md#serial-field-names) section. -However, in this case, the whole enum class must be marked with the [`@Serializable`][Serializable] annotation. - -```kotlin -@Serializable // required because of @SerialName -enum class Status { @SerialName("maintained") SUPPORTED } - -@Serializable -class Project(val name: String, val status: Status) - -fun main() { - val data = Project("kotlinx.serialization", Status.SUPPORTED) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-05.kt). - -We see that the specified serial name is now used in the resulting JSON. - -```text -{"name":"kotlinx.serialization","status":"maintained"} -``` - - - -## Composites - -A number of composite types from the standard library are supported by Kotlin Serialization. - -### Pair and triple - -The simple data classes [Pair] and [Triple] from the Kotlin standard library are serializable. - -```kotlin -@Serializable -class Project(val name: String) - -fun main() { - val pair = 1 to Project("kotlinx.serialization") - println(Json.encodeToString(pair)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-06.kt). - -```text -{"first":1,"second":{"name":"kotlinx.serialization"}} -``` - - - -> Not all classes from the Kotlin standard library are serializable. In particular, ranges and the [Regex] class -> are not serializable at the moment. Support for their serialization may be added in the future. - -### Lists - -A [List] of serializable classes can be serialized. - -```kotlin -@Serializable -class Project(val name: String) - -fun main() { - val list = listOf( - Project("kotlinx.serialization"), - Project("kotlinx.coroutines") - ) - println(Json.encodeToString(list)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-07.kt). - -The result is represented as a list in JSON. - -```text -[{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}] -``` - - - -### Sets and other collections - -Other collections, like [Set], are also serializable. - -```kotlin -@Serializable -class Project(val name: String) - -fun main() { - val set = setOf( - Project("kotlinx.serialization"), - Project("kotlinx.coroutines") - ) - println(Json.encodeToString(set)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-08.kt). - -[Set] is also represented as a list in JSON, like all other collections. - -```text -[{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}] -``` - - - -### Deserializing collections - -During deserialization, the type of the resulting object is determined by the static type that was specified -in the source code—either as the type of the property or as the type parameter of the decoding function. -The following example shows how the same JSON list of integers is deserialized into two properties of -different Kotlin types. - -```kotlin -@Serializable -data class Data( - val a: List, - val b: Set -) - -fun main() { - val data = Json.decodeFromString(""" - { - "a": [42, 42], - "b": [42, 42] - } - """) - println(data) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-09.kt). - -Because the `data.b` property is a [Set], the duplicate values from it disappeared. - -```text -Data(a=[42, 42], b=[42]) -``` - - - -### Maps - -A [Map] with primitive or enum keys and arbitrary serializable values can be serialized. - -```kotlin -@Serializable -class Project(val name: String) - -fun main() { - val map = mapOf( - 1 to Project("kotlinx.serialization"), - 2 to Project("kotlinx.coroutines") - ) - println(Json.encodeToString(map)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-10.kt). - -Kotlin maps in JSON are represented as objects. In JSON object keys are always strings, so keys are encoded as strings -even if they are numbers in Kotlin, as we can see below. - -```text -{"1":{"name":"kotlinx.serialization"},"2":{"name":"kotlinx.coroutines"}} -``` - - - -> It is a JSON-specific limitation that keys cannot be composite. -> It can be lifted as shown in the [Allowing structured map keys](json.md#allowing-structured-map-keys) section. - - -### Unit and singleton objects - -The Kotlin builtin `Unit` type is also serializable. -`Unit` is a Kotlin [singleton object](https://kotlinlang.org/docs/tutorials/kotlin-for-py/objects-and-companion-objects.html), -and is handled equally with other Kotlin objects. - -Conceptually, a singleton is a class with only one instance, meaning that state does not define the object, -but the object defines its state. In JSON, objects are serialized as empty structures. - -```kotlin -@Serializable -object SerializationVersion { - val libraryVersion: String = "1.0.0" -} - -fun main() { - println(Json.encodeToString(SerializationVersion)) - println(Json.encodeToString(Unit)) -} -``` - -> You can get the full code [here](../../guide/example/example-builtin-11.kt). - -While it may seem useless at first glance, this comes in handy for sealed class serialization, -which is explained in the [Polymorphism. Objects](polymorphism.md#objects) section. - -```text -{} -{} -``` - - - -> Serialization of objects is format specific. Other formats may represent objects differently, -> e.g. using their fully-qualified names. - -### Duration - -Since Kotlin `1.7.20` the [Duration] class has become serializable. - - - -```kotlin -fun main() { - val duration = 1000.toDuration(DurationUnit.SECONDS) - println(Json.encodeToString(duration)) -} -``` -> You can get the full code [here](../../guide/example/example-builtin-12.kt). - -Duration is serialized as a string in the ISO-8601-2 format. -```text -"PT16M40S" -``` - - - - -## Nothing - -By default, [Nothing] is a serializable class. However, since there are no instances of this class, it is impossible to encode or decode its values - any attempt will cause an exception. - -This serializer is used when syntactically some type is needed, but it is not actually used in serialization. For example, when using parameterized polymorphic base classes: -```kotlin -@Serializable -sealed class ParametrizedParent { - @Serializable - data class ChildWithoutParameter(val value: Int) : ParametrizedParent() -} - -fun main() { - println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42))) -} -``` -> You can get the full code [here](../../guide/example/example-builtin-13.kt). - -When encoding, the serializer for `Nothing` was not used - -```text -{"value":42} -``` - - - ---- - -The next chapter covers [Serializers](serializers.md). - - -[Double.NaN]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html -[Pair]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/ -[Triple]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/ -[Regex]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/ -[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/ -[Set]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/ -[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/ -[Duration]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/ -[Nothing]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html - - - - -[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html -[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html - - - - -[LongAsStringSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-long-as-string-serializer/index.html - - diff --git a/docs/topics/configure-json-serialization.md b/docs/topics/configure-json-serialization.md index c128c54d85..c32c6c9943 100644 --- a/docs/topics/configure-json-serialization.md +++ b/docs/topics/configure-json-serialization.md @@ -4,20 +4,20 @@ JSON serialization in Kotlin allows you to easily convert Kotlin objects to JSON The [`Json`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/) class is the primary tool for this, offering flexibility in how JSON is generated and parsed. You can configure `Json` instances to handle specific JSON behaviors or use it as is for basic tasks. -The key features include: +The key features of the `Json` class include: -* Serialization of Kotlin objects to JSON strings using `Json.encodeToString`. -* Deserialization of JSON strings back into Kotlin objects with `Json.decodeFromString`. -* Working directly with `JsonElement` for more complex JSON structures using `Json.encodeToJsonElement` and `Json.decodeFromJsonElement`. +* Serialization of Kotlin objects to JSON strings using the `encodeToString()` function. +* Deserialization of JSON strings back into Kotlin objects with the `decodeFromString()` function. +* Working directly with `JsonElement` for more complex JSON structures using the `encodeToJsonElement()` and the `decodeFromJsonElement()` functions. -Import the necessary libraries to use the `Json` class for JSON serialization and deserialization: +To use the `Json` class for JSON serialization and deserialization import the necessary libraries: ```kotlin import kotlinx.serialization.* import kotlinx.serialization.json.* ``` -Let's look at a simple example: +Here's a simple example that demonstrates how JSON serialization works in Kotlin: ```kotlin // Imports the necessary libraries @@ -46,7 +46,8 @@ fun main() { } ``` -Additionally, you can [customize the `Json` instance](serialization-json-configuration.md) to handle specific needs, such as ignoring unknown keys: +Additionally, you can [customize the `Json` instance](serialization-json-configuration.md) to address different use cases, +such as ignoring unknown keys: ```kotlin // Configures a Json instance to ignore unknown keys @@ -57,6 +58,6 @@ val customJson = Json { ## What's next? -* Learn how to [customize JSON serialization settings](serialization-json-configuration.md) to fit your specific needs. +* Learn how to [customize JSON serialization settings](serialization-json-configuration.md) to address different use cases. * Explore [advanced JSON element handling](serialization-json-elements.md) to manipulate and work with JSON data before it is parsed or serialized. * Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. \ No newline at end of file diff --git a/docs/topics/create-custom-serializers.md b/docs/topics/create-custom-serializers.md index c9d06b1c89..81c3f1bf73 100644 --- a/docs/topics/create-custom-serializers.md +++ b/docs/topics/create-custom-serializers.md @@ -517,7 +517,7 @@ Box(contents=Project(name=kotlinx.serialization)) _Contextual serialization_ allows you to adjust the serialization strategy for specific types at runtime, based on the context in which they are used. Unlike static serialization, which is fully defined at compile-time, contextual serialization lets you modify how objects are serialized deep within an object tree. -For example, you could serialize `java.util.Date` in JSON format either as an ISO 8601 String or as a Long, depending on the protocol version being used with contextual serialization. +For example, you could serialize `java.util.Date` in JSON format either as an ISO 8601 `String` or as a `Long`, depending on the protocol version being used with contextual serialization. This approach is supported by the built-in [`ContextualSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual-serializer/) class. To implement contextual serialization: diff --git a/docs/topics/formats.md b/docs/topics/formats.md deleted file mode 100644 index 18cacdd874..0000000000 --- a/docs/topics/formats.md +++ /dev/null @@ -1,1524 +0,0 @@ - - -# Alternative and custom formats (experimental) - -This is the sixth chapter of the [Kotlin Serialization Guide](serialization-guide.md). -It goes beyond JSON, covering alternative and custom formats. Unlike JSON, which is -stable, these are currently experimental features of Kotlin Serialization. - -**Table of contents** - - - -* [CBOR (experimental)](#cbor-experimental) - * [Ignoring unknown keys](#ignoring-unknown-keys) - * [Byte arrays and CBOR data types](#byte-arrays-and-cbor-data-types) -* [ProtoBuf (experimental)](#protobuf-experimental) - * [Field numbers](#field-numbers) - * [Integer types](#integer-types) - * [Lists as repeated fields](#lists-as-repeated-fields) - * [Packed fields](#packed-fields) - * [Oneof field (experimental)](#oneof-field-experimental) - * [Usage](#usage) - * [Alternative](#alternative) - * [ProtoBuf schema generator (experimental)](#protobuf-schema-generator-experimental) -* [Properties (experimental)](#properties-experimental) -* [Custom formats (experimental)](#custom-formats-experimental) - * [Basic encoder](#basic-encoder) - * [Basic decoder](#basic-decoder) - * [Sequential decoding](#sequential-decoding) - * [Adding collection support](#adding-collection-support) - * [Adding null support](#adding-null-support) - * [Efficient binary format](#efficient-binary-format) - * [Format-specific types](#format-specific-types) - - - -## CBOR (experimental) - -[CBOR][RFC 7049] is one of the standard compact binary -encodings for JSON, so it supports a subset of [JSON features](json.md) and -is generally very similar to JSON in use, but produces binary data. - -> CBOR support is (experimentally) available in a separate -> `org.jetbrains.kotlinx:kotlinx-serialization-cbor:` module. - -[Cbor] class has [Cbor.encodeToByteArray] and [Cbor.decodeFromByteArray] functions. -Let us take the basic example from the [JSON encoding](basic-serialization.md#json-encoding), -but encode it using CBOR. - - - -```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val bytes = Cbor.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - val obj = Cbor.decodeFromByteArray(bytes) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-01.kt). - -We print a filtered ASCII representation of the output, writing non-ASCII data in hex, so we see how -all the original strings are directly represented in CBOR, but the format delimiters themselves are binary. - -```text -{BF}dnameukotlinx.serializationhlanguagefKotlin{FF} -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -In [CBOR hex notation](http://cbor.me/), the output is equivalent to the following: -``` -BF # map(*) - 64 # text(4) - 6E616D65 # "name" - 75 # text(21) - 6B6F746C696E782E73657269616C697A6174696F6E # "kotlinx.serialization" - 68 # text(8) - 6C616E6775616765 # "language" - 66 # text(6) - 4B6F746C696E # "Kotlin" - FF # primitive(*) -``` - -> Note, CBOR as a format, unlike JSON, supports maps with non-trivial keys -> (see the [Allowing structured map keys](json.md#allowing-structured-map-keys) section for JSON workarounds), -> and Kotlin maps are serialized as CBOR maps, but some parsers (like `jackson-dataformat-cbor`) don't support this. - -### Ignoring unknown keys - -CBOR format is often used to communicate with [IoT] devices where new properties could be added as a part of a device's -API evolution. By default, unknown keys encountered during deserialization produce an error. -This behavior can be configured with the [ignoreUnknownKeys][CborBuilder.ignoreUnknownKeys] property. - - - -```kotlin -val format = Cbor { ignoreUnknownKeys = true } - -@Serializable -data class Project(val name: String) - -fun main() { - val data = format.decodeFromHexString( - "bf646e616d65756b6f746c696e782e73657269616c697a6174696f6e686c616e6775616765664b6f746c696eff" - ) - println(data) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-02.kt). - -It decodes the object, despite the fact that `Project` is missing the `language` property. - -```text -Project(name=kotlinx.serialization) -``` - - - -In [CBOR hex notation](http://cbor.me/), the input is equivalent to the following: -``` -BF # map(*) - 64 # text(4) - 6E616D65 # "name" - 75 # text(21) - 6B6F746C696E782E73657269616C697A6174696F6E # "kotlinx.serialization" - 68 # text(8) - 6C616E6775616765 # "language" - 66 # text(6) - 4B6F746C696E # "Kotlin" - FF # primitive(*) -``` - -### Byte arrays and CBOR data types - -Per the [RFC 7049 Major Types] section, CBOR supports the following data types: - -- Major type 0: an unsigned integer -- Major type 1: a negative integer -- **Major type 2: a byte string** -- Major type 3: a text string -- **Major type 4: an array of data items** -- Major type 5: a map of pairs of data items -- Major type 6: optional semantic tagging of other major types -- Major type 7: floating-point numbers and simple data types that need no content, as well as the "break" stop code - -By default, Kotlin `ByteArray` instances are encoded as **major type 4**. -When **major type 2** is desired, then the [`@ByteString`][ByteString] annotation can be used. - - - -```kotlin -@Serializable -data class Data( - @ByteString - val type2: ByteArray, // CBOR Major type 2 - val type4: ByteArray // CBOR Major type 4 -) - -fun main() { - val data = Data(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8)) - val bytes = Cbor.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - val obj = Cbor.decodeFromByteArray(bytes) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-03.kt). - -As we see, the CBOR byte that precedes the data is different for different types of encoding. - -```text -{BF}etype2D{01}{02}{03}{04}etype4{9F}{05}{06}{07}{08}{FF}{FF} -Data(type2=[1, 2, 3, 4], type4=[5, 6, 7, 8]) -``` - - - -In [CBOR hex notation](http://cbor.me/), the output is equivalent to the following: -``` -BF # map(*) - 65 # text(5) - 7479706532 # "type2" - 44 # bytes(4) - 01020304 # "\x01\x02\x03\x04" - 65 # text(5) - 7479706534 # "type4" - 9F # array(*) - 05 # unsigned(5) - 06 # unsigned(6) - 07 # unsigned(7) - 08 # unsigned(8) - FF # primitive(*) - FF # primitive(*) -``` - - -## ProtoBuf (experimental) - -[Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally -relies on a separate ".proto" file that defines the protocol schema. It is more compact than CBOR, because it -assigns integer numbers to fields instead of names. - -> Protocol buffers support is (experimentally) available in a separate -> `org.jetbrains.kotlinx:kotlinx-serialization-protobuf:` module. - -Kotlin Serialization is using proto2 semantics, where all fields are explicitly required or optional. -For a basic example we change our example to use the -[ProtoBuf] class with [ProtoBuf.encodeToByteArray] and [ProtoBuf.decodeFromByteArray] functions. - - - -```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val bytes = ProtoBuf.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - val obj = ProtoBuf.decodeFromByteArray(bytes) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-04.kt). - -```text -{0A}{15}kotlinx.serialization{12}{06}Kotlin -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following: -``` -Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization" -Field #2: 12 String Length = 6, Hex = 06, UTF8 = "Kotlin" -``` - -### Field numbers - -By default, field numbers in the Kotlin Serialization [ProtoBuf] implementation are automatically assigned, -which does not provide the ability to define a stable data schema that evolves over time. That is normally achieved by -writing a separate ".proto" file. However, with Kotlin Serialization we can get this ability without a separate -schema file, instead using the [ProtoNumber] annotation. - - - -```kotlin -@Serializable -data class Project( - @ProtoNumber(1) - val name: String, - @ProtoNumber(3) - val language: String -) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val bytes = ProtoBuf.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - val obj = ProtoBuf.decodeFromByteArray(bytes) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-05.kt). - -We see in the output that the number for the first property `name` did not change (as it is numbered from one by default), -but it did change for the `language` property. - -```text -{0A}{15}kotlinx.serialization{1A}{06}Kotlin -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following: -``` -Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization" (total 21 chars) -Field #3: 1A String Length = 6, Hex = 06, UTF8 = "Kotlin" -``` - -### Integer types - -Protocol buffers support various integer encodings optimized for different ranges of integers. -They are specified using the [ProtoType] annotation and the [ProtoIntegerType] enum. -The following example shows all three supported options. - - - -```kotlin -@Serializable -class Data( - @ProtoType(ProtoIntegerType.DEFAULT) - val a: Int, - @ProtoType(ProtoIntegerType.SIGNED) - val b: Int, - @ProtoType(ProtoIntegerType.FIXED) - val c: Int -) - -fun main() { - val data = Data(1, -2, 3) - println(ProtoBuf.encodeToByteArray(data).toAsciiHexString()) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-06.kt). - -* The [default][ProtoIntegerType.DEFAULT] is a varint encoding (`intXX`) that is optimized for - small non-negative numbers. The value of `1` is encoded in one byte `01`. -* The [signed][ProtoIntegerType.SIGNED] is a signed ZigZag encoding (`sintXX`) that is optimized for - small signed integers. The value of `-2` is encoded in one byte `03`. -* The [fixed][ProtoIntegerType.FIXED] encoding (`fixedXX`) always uses a fixed number of bytes. - The value of `3` is encoded as four bytes `03 00 00 00`. - -> `uintXX` and `sfixedXX` protocol buffer types are not supported. - -```text -{08}{01}{10}{03}{1D}{03}{00}{00}{00} -``` - - - -In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode) the output is equivalent to the following: -``` -Field #1: 08 Varint Value = 1, Hex = 01 -Field #2: 10 Varint Value = 3, Hex = 03 -Field #3: 1D Fixed32 Value = 3, Hex = 03-00-00-00 -``` - -### Lists as repeated fields - -By default, kotlin lists and other collections are representend as repeated fields. -In the protocol buffers when the list is empty there are no elements in the -stream with the corresponding number. For Kotlin Serialization you must explicitly specify a default of `emptyList()` -for any property of a collection or map type. Otherwise you will not be able deserialize an empty -list, which is indistinguishable in protocol buffers from a missing field. - - - -```kotlin -@Serializable -data class Data( - val a: List = emptyList(), - val b: List = emptyList() -) - -fun main() { - val data = Data(listOf(1, 2, 3), listOf()) - val bytes = ProtoBuf.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - println(ProtoBuf.decodeFromByteArray(bytes)) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-07.kt). - -```text -{08}{01}{08}{02}{08}{03} -Data(a=[1, 2, 3], b=[]) -``` - - - -In [ProtoBuf diagnostic mode](https://protogen.marcgravell.com/decode) the output is equivalent to the following: -``` -Field #1: 08 Varint Value = 1, Hex = 01 -Field #1: 08 Varint Value = 2, Hex = 02 -Field #1: 08 Varint Value = 3, Hex = 03 -``` - -### Packed fields -Collection types (not maps) can be **written** as packed fields when annotated with the `@ProtoPacked` annotation. -Per the standard packed fields can only be used on primitive numeric types. The annotation is ignored on other types. - -Per the [format description](https://developers.google.com/protocol-buffers/docs/encoding#packed) the parser ignores -the annotation, but rather reads list in either packed or repeated format. - -### Oneof field (experimental) - -Kotlin Serialization `ProtoBuf` format supports [oneof](https://protobuf.dev/programming-guides/proto2/#oneof) fields -basing on the [Polymorphism](polymorphism.md) functionality. - -#### Usage - -Given a protobuf message defined like: - -```proto -message Data { - required string name = 1; - oneof phone { - string home_phone = 2; - string work_phone = 3; - } -} -``` - -You can define a kotlin class semantically equal to this message by following these steps: - -* Declare a sealed interface or abstract class, to represent of the `oneof` group, called *the oneof interface*. In our example, oneof interface is `IPhoneType`. -* 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. -* 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`. -* 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`. - - - -```kotlin -// The outer class -@Serializable -data class Data( - @ProtoNumber(1) val name: String, - @ProtoOneOf val phone: IPhoneType?, -) - -// The oneof interface -@Serializable sealed interface IPhoneType - -// Message holder for home_phone -@Serializable @JvmInline value class HomePhone(@ProtoNumber(2) val number: String): IPhoneType - -// 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. -@Serializable data class WorkPhone(@ProtoNumber(3) val number: String): IPhoneType - -fun main() { - val dataTom = Data("Tom", HomePhone("123")) - val stringTom = ProtoBuf.encodeToHexString(dataTom) - val dataJerry = Data("Jerry", WorkPhone("789")) - val stringJerry = ProtoBuf.encodeToHexString(dataJerry) - println(stringTom) - println(stringJerry) - println(ProtoBuf.decodeFromHexString(stringTom)) - println(ProtoBuf.decodeFromHexString(stringJerry)) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-08.kt). - -```text -0a03546f6d1203313233 -0a054a657272791a03373839 -Data(name=Tom, phone=HomePhone(number=123)) -Data(name=Jerry, phone=WorkPhone(number=789)) -``` - - - -In [ProtoBuf diagnostic mode](https://protogen.marcgravell.com/decode) the first 2 lines in the output are equivalent to - -``` -Field #1: 0A String Length = 3, Hex = 03, UTF8 = "Tom" Field #2: 12 String Length = 3, Hex = 03, UTF8 = "123" -Field #1: 0A String Length = 5, Hex = 05, UTF8 = "Jerry" Field #3: 1A String Length = 3, Hex = 03, UTF8 = "789" -``` - -You 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 -another data class. Otherwise, you may get id conflicts or `IllegalArgumentException` in runtime. - -#### Alternative - -You 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. - -For example, the following class: - -``` -@Serializable -data class Data2( - @ProtoNumber(1) val name: String, - @ProtoNumber(2) val homeNumber: String? = null, - @ProtoNumber(3) val workNumber: String? = null, -) -``` - -is 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. - -But 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. - -### ProtoBuf schema generator (experimental) - -As mentioned above, when working with protocol buffers you usually use a ".proto" file and a code generator for your -language. This includes the code to serialize your message to an output stream and deserialize it from an input stream. -When using Kotlin Serialization this step is not necessary because your `@Serializable` Kotlin data types are used as the -source for the schema. - -This is very convenient for Kotlin-to-Kotlin communication, but makes interoperability between languages complicated. -Fortunately, you can use the ProtoBuf schema generator to output the ".proto" representation of your messages. You can -keep your Kotlin classes as a source of truth and use traditional protoc compilers for other languages at the same time. - -As an example, we can display the following data class's ".proto" schema as follows. - - - -```kotlin -@Serializable -data class SampleData( - val amount: Long, - val description: String?, - val department: String = "QA" -) -fun main() { - val descriptors = listOf(SampleData.serializer().descriptor) - val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors) - println(schemas) -} -``` -> You can get the full code [here](../../guide/example/example-formats-09.kt). - -Which would output as follows. - -```text -syntax = "proto2"; - - -// serial name 'example.exampleFormats09.SampleData' -message SampleData { - required int64 amount = 1; - optional string description = 2; - // WARNING: a default value decoded when value is missing - optional string department = 3; -} - -``` - - - -Note that since default values are not represented in ".proto" files, a warning is generated when one appears in the schema. - -See the documentation for [ProtoBufSchemaGenerator] for more information. - -## Properties (experimental) - -Kotlin Serialization can serialize a class into a flat map with `String` keys via -the [Properties][kotlinx.serialization.properties.Properties] format implementation. - -> Properties support is (experimentally) available in a separate -> `org.jetbrains.kotlinx:kotlinx-serialization-properties:` module. - - - -```kotlin -@Serializable -class Project(val name: String, val owner: User) - -@Serializable -class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin")) - val map = Properties.encodeToMap(data) - map.forEach { (k, v) -> println("$k = $v") } -} -``` - -> You can get the full code [here](../../guide/example/example-formats-10.kt). - -The resulting map has dot-separated keys representing keys of the nested objects. - -```text -name = kotlinx.serialization -owner.name = kotlin -``` - - - -## Custom formats (experimental) - -A custom format for Kotlin Serialization must provide an implementation for the [Encoder] and [Decoder] interfaces that -we saw used in the [Serializers](serializers.md) chapter. -These are pretty large interfaces. For convenience -the [AbstractEncoder] and [AbstractDecoder] skeleton implementations are provided to simplify the task. -In [AbstractEncoder] most of the `encodeXxx` methods have a default implementation that -delegates to [`encodeValue(value: Any)`][AbstractEncoder.encodeValue] — the only method that must be -implemented to get a basic working format. - -### Basic encoder - -Let us start with a trivial format implementation that encodes the data into a single list of primitive -constituent objects in the order they were written in the source code. To start, we implement a simple [Encoder] by -overriding `encodeValue` in [AbstractEncoder]. - - - -```kotlin -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } -} -``` - -Now we write a convenience top-level function that creates an encoder that encodes an object -and returns a list. - -```kotlin -fun encodeToList(serializer: SerializationStrategy, value: T): List { - val encoder = ListEncoder() - encoder.encodeSerializableValue(serializer, value) - return encoder.list -} -``` - -For even more convenience, to avoid the need to explicitly pass a serializer, we write an `inline` overload of -the `encodeToList` function with a `reified` type parameter using the [serializer] function to retrieve -the appropriate [KSerializer] instance for the actual type. - -```kotlin -inline fun encodeToList(value: T) = encodeToList(serializer(), value) -``` - -Now we can test it. - -```kotlin -@Serializable -data class Project(val name: String, val owner: User, val votes: Int) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin"), 9000) - println(encodeToList(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-11.kt). - -As a result, we got all the primitive values in our object graph visited and put into a list -in _serial_ order. - -```text -[kotlinx.serialization, kotlin, 9000] -``` - - - -> By itself, that's a useful feature if we need compute some kind of hashcode or digest for all the data -> that is contained in a serializable object tree. - -### Basic decoder - - - -A decoder needs to implement more substance. - -* [decodeValue][AbstractDecoder.decodeValue] — returns the next value from the list. -* [decodeElementIndex][CompositeDecoder.decodeElementIndex] — returns the next index of a deserialized value. - In this primitive format deserialization always happens in order, so we keep track of the index - in the `elementIndex` variable. See - the [Hand-written composite serializer](serializers.md#hand-written-composite-serializer) section - on how it ends up being used. -* [beginStructure][Decoder.beginStructure] — returns a new instance of `ListDecoder`, so that - each structure that is being recursively decoded keeps track of its own `elementIndex` state separately. - -```kotlin -class ListDecoder(val list: ArrayDeque) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list) -} -``` - -A couple of convenience functions for decoding. - -```kotlin -fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { - val decoder = ListDecoder(ArrayDeque(list)) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) -``` - -That is enough to start encoding and decoding basic serializable classes. - - - -```kotlin -fun main() { - val data = Project("kotlinx.serialization", User("kotlin"), 9000) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-12.kt). - -Now we can convert a list of primitives back to an object tree. - -```text -[kotlinx.serialization, kotlin, 9000] -Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000) -``` - - - -### Sequential decoding - -The decoder we have implemented keeps track of the `elementIndex` in its state and implements -`decodeElementIndex`. This means that it is going to work with an arbitrary serializer, even the -simple one we wrote in -the [Hand-written composite serializer](serializers.md#hand-written-composite-serializer) section. -However, this format always stores elements in order, so this bookkeeping is not needed and -undermines decoding performance. All auto-generated serializers on the JVM support -the [Sequential decoding protocol (experimental)](serializers.md#sequential-decoding-protocol-experimental), and the decoder can indicate -its support by returning `true` from the [CompositeDecoder.decodeSequentially] function. - - - -```kotlin -class ListDecoder(val list: ArrayDeque) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list) - - override fun decodeSequentially(): Boolean = true -} -``` - - - -> You can get the full code [here](../../guide/example/example-formats-13.kt). - - - -### Adding collection support - -This basic format, so far, cannot properly represent collections. In encodes them, but it does not keep -track of how many elements there are in the collection or where it ends, so it cannot properly decode them. -First, let us add proper support for collections to the encoder by implementing the -[Encoder.beginCollection] function. The `beginCollection` function takes a collection size as a parameter, -so we encode it to add it to the result. -Our encoder implementation does not keep any state, so it just returns `this` from the `beginCollection` function. - - - -```kotlin -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } - - override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - encodeInt(collectionSize) - return this - } -} -``` - - - -The decoder, for our case, needs to only implement the [CompositeDecoder.decodeCollectionSize] function -in addition to the previous code. - -> The formats that store collection size in advance have to return `true` from `decodeSequentially`. - -```kotlin -class ListDecoder(val list: ArrayDeque, var elementsCount: Int = 0) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list, descriptor.elementsCount) - - override fun decodeSequentially(): Boolean = true - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = - decodeInt().also { elementsCount = it } -} -``` - - - -That is all that is needed to support collections and maps. - -```kotlin -@Serializable -data class Project(val name: String, val owners: List, val votes: Int) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", listOf(User("kotlin"), User("jetbrains")), 9000) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-14.kt). - -We see the size of the list added to the result, letting the decoder know where to stop. - -```text -[kotlinx.serialization, 2, kotlin, jetbrains, 9000] -Project(name=kotlinx.serialization, owners=[User(name=kotlin), User(name=jetbrains)], votes=9000) -``` - - - -### Adding null support - -Our trivial format does not support `null` values so far. For nullable types we need to add some kind -of "null indicator", telling whether the upcoming value is null or not. - - - -In the encoder implementation we override [Encoder.encodeNull] and [Encoder.encodeNotNullMark]. - -```kotlin - override fun encodeNull() = encodeValue("NULL") - override fun encodeNotNullMark() = encodeValue("!!") -``` - - - -In the decoder implementation we override [Decoder.decodeNotNullMark]. - -```kotlin - override fun decodeNotNullMark(): Boolean = decodeString() != "NULL" -``` - - - -Let us test nullable properties both with not-null and null values. - -```kotlin -@Serializable -data class Project(val name: String, val owner: User?, val votes: Int?) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin") , null) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} - -``` - -> You can get the full code [here](../../guide/example/example-formats-15.kt). - -In the output we see how not-null`!!` and `NULL` marks are used. - -```text -[kotlinx.serialization, !!, kotlin, NULL] -Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=null) -``` - - - -### Efficient binary format - -Now we are ready for an example of an efficient binary format. We are going to write data to the -[java.io.DataOutput] implementation. Instead of `encodeValue` we must override the individual -`encodeXxx` functions for each of ten [primitives](builtin-classes.md#primitives) in the encoder. - - - -```kotlin -class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() { - override val serializersModule: SerializersModule = EmptySerializersModule() - override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) - override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) - override fun encodeShort(value: Short) = output.writeShort(value.toInt()) - override fun encodeInt(value: Int) = output.writeInt(value) - override fun encodeLong(value: Long) = output.writeLong(value) - override fun encodeFloat(value: Float) = output.writeFloat(value) - override fun encodeDouble(value: Double) = output.writeDouble(value) - override fun encodeChar(value: Char) = output.writeChar(value.code) - override fun encodeString(value: String) = output.writeUTF(value) - override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index) - - override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - encodeInt(collectionSize) - return this - } - - override fun encodeNull() = encodeBoolean(false) - override fun encodeNotNullMark() = encodeBoolean(true) -} -``` - - - -The decoder implementation mirrors encoder's implementation overriding all the primitive `decodeXxx` functions. - -```kotlin -class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() { - private var elementIndex = 0 - override val serializersModule: SerializersModule = EmptySerializersModule() - override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 - override fun decodeByte(): Byte = input.readByte() - override fun decodeShort(): Short = input.readShort() - override fun decodeInt(): Int = input.readInt() - override fun decodeLong(): Long = input.readLong() - override fun decodeFloat(): Float = input.readFloat() - override fun decodeDouble(): Double = input.readDouble() - override fun decodeChar(): Char = input.readChar() - override fun decodeString(): String = input.readUTF() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - DataInputDecoder(input, descriptor.elementsCount) - - override fun decodeSequentially(): Boolean = true - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = - decodeInt().also { elementsCount = it } - - override fun decodeNotNullMark(): Boolean = decodeBoolean() -} -``` - - - -We can now serialize and deserialize arbitrary data. For example, the same classes as were -used in the [CBOR (experimental)](#cbor-experimental) and [ProtoBuf (experimental)](#protobuf-experimental) sections. - -```kotlin -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val output = ByteArrayOutputStream() - encodeTo(DataOutputStream(output), data) - val bytes = output.toByteArray() - println(bytes.toAsciiHexString()) - val input = ByteArrayInputStream(bytes) - val obj = decodeFrom(DataInputStream(input)) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-16.kt). - -As we can see, the result is a dense binary format that only contains the data that is being serialized. -It can be easily tweaked for any kind of domain-specific compact encoding. - -```text -{00}{15}kotlinx.serialization{00}{06}Kotlin -Project(name=kotlinx.serialization, language=Kotlin) -``` - - - -### Format-specific types - -A format implementation might provide special support for data types that are not among the list of primitive -types in Kotlin Serialization, and do not have a corresponding `encodeXxx`/`decodeXxx` function. -In the encoder this is achieved by overriding the -[`encodeSerializableValue(serializer, value)`][Encoder.encodeSerializableValue] function. - -In our `DataOutput` format example we might want to provide a specialized efficient data path for serializing an array -of bytes since [DataOutput][java.io.DataOutput] has a special method for this purpose. - -Detection of the type is performed by looking at the `serializer.descriptor`, not by checking the type of the `value` -being serialized, so we fetch the builtin [KSerializer] instance for `ByteArray` type. - -> This an important difference. This way our format implementation properly supports -> [Custom serializers](serializers.md#custom-serializers) that a user might specify for a type that just happens -> to be internally represented as a byte array, but need a different serial representation. - - - -```kotlin -private val byteArraySerializer = serializer() -``` - -> Specifically for byte arrays, we could have also used the builtin -> [ByteArraySerializer][kotlinx.serialization.builtins.ByteArraySerializer()] function. - -We add the corresponding code to the [Encoder] implementation of our -[Efficient binary format](#efficient-binary-format). To make our `ByteArray` encoding even more efficient, -we add a trivial implementation of `encodeCompactSize` function that uses only one byte to represent -a size of up to 254 bytes. - - - -```kotlin - override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { - if (serializer.descriptor == byteArraySerializer.descriptor) - encodeByteArray(value as ByteArray) - else - super.encodeSerializableValue(serializer, value) - } - - private fun encodeByteArray(bytes: ByteArray) { - encodeCompactSize(bytes.size) - output.write(bytes) - } - - private fun encodeCompactSize(value: Int) { - if (value < 0xff) { - output.writeByte(value) - } else { - output.writeByte(0xff) - output.writeInt(value) - } - } -``` - - - -A similar code is added to the [Decoder] implementation. Here we override -the [decodeSerializableValue][Decoder.decodeSerializableValue] function. - -```kotlin - @Suppress("UNCHECKED_CAST") - override fun decodeSerializableValue(deserializer: DeserializationStrategy, previousValue: T?): T = - if (deserializer.descriptor == byteArraySerializer.descriptor) - decodeByteArray() as T - else - super.decodeSerializableValue(deserializer, previousValue) - - private fun decodeByteArray(): ByteArray { - val bytes = ByteArray(decodeCompactSize()) - input.readFully(bytes) - return bytes - } - - private fun decodeCompactSize(): Int { - val byte = input.readByte().toInt() and 0xff - if (byte < 0xff) return byte - return input.readInt() - } -``` - - - -Now everything is ready to perform serialization of some byte arrays. - -```kotlin -@Serializable -data class Project(val name: String, val attachment: ByteArray) - -fun main() { - val data = Project("kotlinx.serialization", byteArrayOf(0x0A, 0x0B, 0x0C, 0x0D)) - val output = ByteArrayOutputStream() - encodeTo(DataOutputStream(output), data) - val bytes = output.toByteArray() - println(bytes.toAsciiHexString()) - val input = ByteArrayInputStream(bytes) - val obj = decodeFrom(DataInputStream(input)) - println(obj) -} -``` - -> You can get the full code [here](../../guide/example/example-formats-17.kt). - -As we can see, our custom byte array format is being used, with the compact encoding of its size in one byte. - -```text -{00}{15}kotlinx.serialization{04}{0A}{0B}{0C}{0D} -Project(name=kotlinx.serialization, attachment=[10, 11, 12, 13]) -``` - - - ---- - -This chapter concludes [Kotlin Serialization Guide](serialization-guide.md). - - - -[RFC 7049]: https://tools.ietf.org/html/rfc7049 -[IoT]: https://en.wikipedia.org/wiki/Internet_of_things -[RFC 7049 Major Types]: https://tools.ietf.org/html/rfc7049#section-2.1 - - -[java.io.DataOutput]: https://docs.oracle.com/javase/8/docs/api/java/io/DataOutput.html - - - - -[serializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/serializer.html -[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html - - - -[kotlinx.serialization.builtins.ByteArraySerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-byte-array-serializer.html - - - -[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html -[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html -[AbstractEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/index.html -[AbstractDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/index.html -[AbstractEncoder.encodeValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/encode-value.html -[AbstractDecoder.decodeValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/decode-value.html -[CompositeDecoder.decodeElementIndex]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html -[Decoder.beginStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/begin-structure.html -[CompositeDecoder.decodeSequentially]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html -[Encoder.beginCollection]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/begin-collection.html -[CompositeDecoder.decodeCollectionSize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-collection-size.html -[Encoder.encodeNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-null.html -[Encoder.encodeNotNullMark]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-not-null-mark.html -[Decoder.decodeNotNullMark]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-not-null-mark.html -[Encoder.encodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html -[Decoder.decodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html - - - - -[kotlinx.serialization.properties.Properties]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-properties/kotlinx.serialization.properties/-properties/index.html - - - - -[ProtoBuf]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/index.html -[ProtoBuf.encodeToByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/encode-to-byte-array.html -[ProtoBuf.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/decode-from-byte-array.html -[ProtoNumber]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-number/index.html -[ProtoType]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-type/index.html -[ProtoIntegerType]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/index.html -[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 -[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 -[ProtoIntegerType.FIXED]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/index.html - - - -[ProtoBufSchemaGenerator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema/-proto-buf-schema-generator/index.html - - - - -[Cbor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/index.html -[Cbor.encodeToByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/encode-to-byte-array.html -[Cbor.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/decode-from-byte-array.html -[CborBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html -[ByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/index.html - - diff --git a/docs/topics/json.md b/docs/topics/json.md deleted file mode 100644 index 321c61cfe7..0000000000 --- a/docs/topics/json.md +++ /dev/null @@ -1,35 +0,0 @@ - -# JSON features - -This is the fifth chapter of the [Kotlin Serialization Guide](serialization-guide.md). -In this chapter, we'll walk through features of [JSON](https://www.json.org/json-en.html) serialization available in the [Json] class. - -**Table of contents** - - - - ---- - -The next chapter covers [Alternative and custom formats (experimental)](formats.md). - - - -[RFC-4627]: https://www.ietf.org/rfc/rfc4627.txt -[BigDecimal]: https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html - - -[Double]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/ -[Double.NaN]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html -[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/ -[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/ - - - - - - - -[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html - - diff --git a/docs/topics/polymorphism.md b/docs/topics/polymorphism.md deleted file mode 100644 index 0b340c1cc8..0000000000 --- a/docs/topics/polymorphism.md +++ /dev/null @@ -1,1040 +0,0 @@ - - -# Polymorphism - -This is the fourth chapter of the [Kotlin Serialization Guide](serialization-guide.md). -In this chapter we'll see how Kotlin Serialization deals with polymorphic class hierarchies. - -**Table of contents** - - - -* [Closed polymorphism](#closed-polymorphism) - * [Static types](#static-types) - * [Designing serializable hierarchy](#designing-serializable-hierarchy) - * [Sealed classes](#sealed-classes) - * [Custom subclass serial name](#custom-subclass-serial-name) - * [Concrete properties in a base class](#concrete-properties-in-a-base-class) - * [Objects](#objects) -* [Open polymorphism](#open-polymorphism) - * [Registered subclasses](#registered-subclasses) - * [Serializing interfaces](#serializing-interfaces) - * [Property of an interface type](#property-of-an-interface-type) - * [Static parent type lookup for polymorphism](#static-parent-type-lookup-for-polymorphism) - * [Explicitly marking polymorphic class properties](#explicitly-marking-polymorphic-class-properties) - * [Registering multiple superclasses](#registering-multiple-superclasses) - * [Polymorphism and generic classes](#polymorphism-and-generic-classes) - * [Merging library serializers modules](#merging-library-serializers-modules) - * [Default polymorphic type handler for deserialization](#default-polymorphic-type-handler-for-deserialization) - * [Default polymorphic type handler for serialization](#default-polymorphic-type-handler-for-serialization) - - - - - -## Closed polymorphism - -Let us start with basic introduction to polymorphism. - -### Static types - -Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined -by *compile-time* types of objects. Let's examine this aspect in more detail and learn how -to serialize polymorphic data structures, where the type of data is determined at runtime. - -To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project` -has just the `name` property, while its derived `class OwnedProject` adds an `owner` property. -In the below example, we serialize `data` variable with a static type of -`Project` that is initialized with an instance of `OwnedProject` at runtime. - -```kotlin -@Serializable -open class Project(val name: String) - -class OwnedProject(name: String, val owner: String) : Project(name) - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-01.kt). - -Despite the runtime type of `OwnedProject`, only the `Project` class properties are getting serialized. - -```text -{"name":"kotlinx.coroutines"} -``` - - - -Let's change the compile-time type of `data` to `OwnedProject`. - -```kotlin -@Serializable -open class Project(val name: String) - -class OwnedProject(name: String, val owner: String) : Project(name) - -fun main() { - val data = OwnedProject("kotlinx.coroutines", "kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-02.kt). - -We get an error, because the `OwnedProject` class is not serializable. - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found. -Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. -``` - - - -### Designing serializable hierarchy - -We cannot simply mark `OwnedProject` from the previous example as `@Serializable`. It does not compile, -running into the [constructor properties requirement](basic-serialization.md#constructor-properties-requirement). -To make hierarchy of classes serializable, the properties in the parent class have to be marked `abstract`, -making the `Project` class `abstract`, too. - -```kotlin -@Serializable -abstract class Project { - abstract val name: String -} - -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-03.kt). - -This is close to the best design for a serializable hierarchy of classes, but running it produces the following error: - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'. -Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule. -To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'. -``` - - - -### Sealed classes - -The most straightforward way to use serialization with a polymorphic hierarchy is to mark the base class `sealed`. -_All_ subclasses of a sealed class must be explicitly marked as `@Serializable`. - -```kotlin -@Serializable -sealed class Project { - abstract val name: String -} - -@Serializable -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(Json.encodeToString(data)) // Serializing data of compile-time type Project -} -``` - -> You can get the full code [here](../../guide/example/example-poly-04.kt). - -Now we can see a default way to represent polymorphism in JSON. -A `type` key is added to the resulting JSON object as a _discriminator_. - -```text -{"type":"example.examplePoly04.OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"} -``` - - - -Pay attention to the small, but very important detail in the above example that is related to [Static types](#static-types): -the `val data` property has a compile-time type of `Project`, even though its run-time type is `OwnedProject`. -When serializing polymorphic class hierarchies you must ensure that the compile-time type of the serialized object -is a polymorphic one, not a concrete one. - -Let us see what happens if the example is slightly changed, so that the compile-time of the object that is being -serialized is `OwnedProject` (the same as its run-time type). - -```kotlin -@Serializable -sealed class Project { - abstract val name: String -} - -@Serializable -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data = OwnedProject("kotlinx.coroutines", "kotlin") // data: OwnedProject here - println(Json.encodeToString(data)) // Serializing data of compile-time type OwnedProject -} -``` - -> You can get the full code [here](../../guide/example/example-poly-05.kt). - -The type of `OwnedProject` is concrete and is not polymorphic, thus the `type` -discriminator property is not emitted into the resulting JSON. - -```text -{"name":"kotlinx.coroutines","owner":"kotlin"} -``` - - - -In general, Kotlin Serialization is designed to work correctly only when the compile-time type used during serialization -is the same one as the compile-time type used during deserialization. You can always specify the type explicitly -when calling serialization functions. The previous example can be corrected to use `Project` type for serialization -by calling `Json.encodeToString(data)`. - -### Custom subclass serial name - -A value of the `type` key is a fully qualified class name by default. We can put [SerialName] annotation onto -the corresponding class to change it. - -```kotlin -@Serializable -sealed class Project { - abstract val name: String -} - -@Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-06.kt). - -This way we can have a stable _serial name_ that is not affected by the class's name in the source code. - -```text -{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} -``` - - - -> In addition to that, JSON can be configured to use a different key name for the class discriminator. -> You can find an example in the [Class discriminator for polymorphism](json.md#class-discriminator-for-polymorphism) section. - -### Concrete properties in a base class - -A base class in a sealed hierarchy can have properties with backing fields. - -```kotlin -@Serializable -sealed class Project { - abstract val name: String - var status = "open" -} - -@Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val json = Json { encodeDefaults = true } // "status" will be skipped otherwise - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-07.kt). - -The properties of the superclass are serialized before the properties of the subclass. - -```text -{"type":"owned","status":"open","name":"kotlinx.coroutines","owner":"kotlin"} -``` - - - -### Objects - -Sealed hierarchies can have objects as their subclasses and they also need to be marked as `@Serializable`. -Let's take a different example with a hierarchy of `Response` classes. - -```kotlin -@Serializable -sealed class Response - -@Serializable -object EmptyResponse : Response() - -@Serializable -class TextResponse(val text: String) : Response() -``` - -Let us serialize a list of different responses. - -```kotlin -fun main() { - val list = listOf(EmptyResponse, TextResponse("OK")) - println(Json.encodeToString(list)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-08.kt). - -An object serializes as an empty class, also using its fully-qualified class name as type by default: - -```text -[{"type":"example.examplePoly08.EmptyResponse"},{"type":"example.examplePoly08.TextResponse","text":"OK"}] -``` - - - -> Even if object has properties, they are not serialized. - -## Open polymorphism - -Serialization can work with arbitrary `open` classes or `abstract` classes. -However, since this kind of polymorphism is open, there is a possibility that subclasses are defined anywhere in the -source code, even in other modules, the list of subclasses that are serialized cannot be determined at compile-time and -must be explicitly registered at runtime. - -### Registered subclasses - -Let us start with the code from the [Designing serializable hierarchy](#designing-serializable-hierarchy) section. -To make it work with serialization without making it `sealed`, we have to define a [SerializersModule] using the -[SerializersModule {}][SerializersModule()] builder function. In the module the base class is specified -in the [polymorphic][_polymorphic] builder and each subclass is registered with the [subclass] function. Now, -a custom JSON configuration can be instantiated with this module and used for serialization. - -> Details on custom JSON configurations can be found in -> the [JSON configuration](json.md#json-configuration) section. - - - -```kotlin -val module = SerializersModule { - polymorphic(Project::class) { - subclass(OwnedProject::class) - } -} - -val format = Json { serializersModule = module } - -@Serializable -abstract class Project { - abstract val name: String -} - -@Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-09.kt). - -This additional configuration makes our code work just as it worked with a sealed class in -the [Sealed classes](#sealed-classes) section, but here subclasses can be spread arbitrarily throughout the code. - -```text -{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} -``` - - ->Please note that this example works only on JVM because of `serializer` function restrictions. ->For JS and Native, explicit serializer should be used: `format.encodeToString(PolymorphicSerializer(Project::class), data)` ->You can keep track of this issue [here](https://github.com/Kotlin/kotlinx.serialization/issues/1077). - -### Serializing interfaces - -We can update the previous example and turn `Project` superclass into an interface. However, we cannot -mark an interface itself as `@Serializable`. No problem. Interfaces cannot have instances by themselves. -Interfaces can only be represented by instances of their derived classes. Interfaces are used in the Kotlin language to enable polymorphism, -so all interfaces are considered to be implicitly serializable with the [PolymorphicSerializer] -strategy. We just need to mark their implementing classes as `@Serializable` and register them. - - - -```kotlin -interface Project { - val name: String -} - -@Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project -``` - -Now if we declare `data` with the type of `Project` we can simply call `format.encodeToString` as before. - -```kotlin -fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-10.kt). - -```text -{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} -``` - -> Note: On Kotlin/Native, you should use `format.encodeToString(PolymorphicSerializer(Project::class), data))` instead due to limited reflection capabilities. - - - -### Property of an interface type - -Continuing the previous example, let us see what happens if we use `Project` interface as a property in some -other serializable class. Interfaces are implicitly polymorphic, so we can just declare a property of an interface type. - - - -```kotlin -@Serializable -class Data(val project: Project) // Project is an interface - -fun main() { - val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-11.kt). - -As long as we've registered the actual subtype of the interface that is being serialized in -the [SerializersModule] of our `format`, we get it working at runtime. - -```text -{"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} -``` - - - -### Static parent type lookup for polymorphism - -During serialization of a polymorphic class the root type of the polymorphic hierarchy (`Project` in our example) -is determined statically. Let us take the example with the serializable `abstract class Project`, -but change the `main` function to declare `data` as having a type of `Any`: - - - -```kotlin -fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-12.kt). - -We get the exception. - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. -Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. -``` - - - -We have to register classes for polymorphic serialization with respect for the corresponding static type we -use in the source code. First of all, we change our module to register a subclass of `Any`: - - - -```kotlin -val module = SerializersModule { - polymorphic(Any::class) { - subclass(OwnedProject::class) - } -} -``` - - - -Then we can try to serialize the variable of type `Any`: - -```kotlin -fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-13.kt). - -However, `Any` is a class and it is not serializable: - -```text -Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. -Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. -``` - - - -We must to explicitly pass an instance of [PolymorphicSerializer] for the base class `Any` as the -first parameter to the [encodeToString][Json.encodeToString] function. - - - -```kotlin -fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(PolymorphicSerializer(Any::class), data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-14.kt). - -With the explicit serializer it works as before. - -```text -{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} -``` - - - -### Explicitly marking polymorphic class properties - -The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism. -However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type. -If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization -strategy via the [`@Serializable`][Serializable] annotation as we saw in -the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section. -To specify a polymorphic serialization strategy of a property, the special-purpose [`@Polymorphic`][Polymorphic] -annotation is used. - - - -```kotlin -@Serializable -class Data( - @Polymorphic // the code does not compile without it - val project: Any -) - -fun main() { - val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-15.kt). - - - -### Registering multiple superclasses - -When the same class gets serialized as a value of properties with different compile-time type from the list of -its superclasses, we must register it in the [SerializersModule] for each of its superclasses separately. -It is convenient to extract registration of all the subclasses into a separate function and -use it for each superclass. You can use the following template to write it. - - - -```kotlin -val module = SerializersModule { - fun PolymorphicModuleBuilder.registerProjectSubclasses() { - subclass(OwnedProject::class) - } - polymorphic(Any::class) { registerProjectSubclasses() } - polymorphic(Project::class) { registerProjectSubclasses() } -} -``` - - - -> You can get the full code [here](../../guide/example/example-poly-16.kt). - - - -### Polymorphism and generic classes - -Generic subtypes for a serializable class require a special handling. Consider the following hierarchy. - - - -```kotlin -@Serializable -abstract class Response - -@Serializable -@SerialName("OkResponse") -data class OkResponse(val data: T) : Response() -``` - -Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the -type parameter `T` when serializing a property of the polymorphic type `OkResponse`. We have to provide this -strategy explicitly when defining the serializers module for `Response`. In the below example we -use `OkResponse.serializer(...)` to retrieve -the [Plugin-generated generic serializer](serializers.md#plugin-generated-generic-serializer) of -the `OkResponse` class and instantiate it with the [PolymorphicSerializer] instance with -`Any` class as its base. This way, we can serialize an instance of `OkResponse` with any `data` property that -was polymorphically registered as a subtype of `Any`. - -```kotlin -val responseModule = SerializersModule { - polymorphic(Response::class) { - subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) - } -} -``` - -### Merging library serializers modules - -When the application grows in size and splits into source code modules, -it may become inconvenient to store all class hierarchies in one serializers module. -Let us add a library with the `Project` hierarchy to the code from the previous section. - -```kotlin -val projectModule = SerializersModule { - fun PolymorphicModuleBuilder.registerProjectSubclasses() { - subclass(OwnedProject::class) - } - polymorphic(Any::class) { registerProjectSubclasses() } - polymorphic(Project::class) { registerProjectSubclasses() } -} -``` - - - -We can compose those two modules together using the [plus] operator to merge them, -so that we can use them both in the same [Json] format instance. - -> You can also use the [include][SerializersModuleBuilder.include] function -> in the [SerializersModule {}][SerializersModule()] DSL. - -```kotlin -val format = Json { serializersModule = projectModule + responseModule } -```` - -Now classes from both hierarchies can be serialized together and deserialized together. - -```kotlin -fun main() { - // both Response and Project are abstract and their concrete subtypes are being serialized - val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) - val string = format.encodeToString(data) - println(string) - println(format.decodeFromString>(string)) -} - -``` - -> You can get the full code [here](../../guide/example/example-poly-17.kt). - -The JSON that is being produced is deeply polymorphic. - -```text -{"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} -OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) -``` - - - -If you're writing a library or shared module with an abstract class and some implementations of it, -you can expose your own serializers module for your clients to use so that a client can combine your -module with their modules. - -### Default polymorphic type handler for deserialization - -What happens when we deserialize a subclass that was not registered? - - - -```kotlin -fun main() { - println(format.decodeFromString(""" - {"type":"unknown","name":"example"} - """)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-18.kt). - -We get the following exception. - -```text -Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $ -Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule. -``` - - - -When reading a flexible input we might want to provide some default behavior in this case. For example, -we can have a `BasicProject` subtype to represent all kinds of unknown `Project` subtypes. - - - -```kotlin -@Serializable -abstract class Project { - abstract val name: String -} - -@Serializable -data class BasicProject(override val name: String, val type: String): Project() - -@Serializable -@SerialName("OwnedProject") -data class OwnedProject(override val name: String, val owner: String) : Project() -``` - -We register a default deserializer handler using the [`defaultDeserializer`][PolymorphicModuleBuilder.defaultDeserializer] function in -the [`polymorphic { ... }`][PolymorphicModuleBuilder] DSL that defines a strategy which maps the `type` string from the input -to the [deserialization strategy][DeserializationStrategy]. In the below example we don't use the type, -but always return the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) -of the `BasicProject` class. - -```kotlin -val module = SerializersModule { - polymorphic(Project::class) { - subclass(OwnedProject::class) - defaultDeserializer { BasicProject.serializer() } - } -} -``` - -Using this module we can now deserialize both instances of the registered `OwnedProject` and -any unregistered one. - -```kotlin -val format = Json { serializersModule = module } - -fun main() { - println(format.decodeFromString>(""" - [ - {"type":"unknown","name":"example"}, - {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} - ] - """)) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-19.kt). - -Notice, how `BasicProject` had also captured the specified type key in its `type` property. - -```text -[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)] -``` - - - -We used a plugin-generated serializer as a default serializer, implying that -the structure of the "unknown" data is known in advance. In a real-world API it's rarely the case. -For that purpose a custom, less-structured serializer is needed. You will see the example of such serializer in the future section -on [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attributes). - -### Default polymorphic type handler for serialization - -Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you -don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can register a default serializer. - - - -```kotlin -interface Animal { -} - -interface Cat : Animal { - val catType: String -} - -interface Dog : Animal { - val dogType: String -} - -private class CatImpl : Cat { - override val catType: String = "Tabby" -} - -private class DogImpl : Dog { - override val dogType: String = "Husky" -} - -object AnimalProvider { - fun createCat(): Cat = CatImpl() - fun createDog(): Dog = DogImpl() -} -``` - -We register a default serializer handler using the [`polymorphicDefaultSerializer`][SerializersModuleBuilder.polymorphicDefaultSerializer] function in -the [`SerializersModule { ... }`][SerializersModuleBuilder] DSL that defines a strategy which takes an instance of the base class and -provides a [serialization strategy][SerializationStrategy]. In the below example we use a `when` block to check the type of the -instance, without ever having to refer to the private implementation classes. - -```kotlin -val module = SerializersModule { - polymorphicDefaultSerializer(Animal::class) { instance -> - @Suppress("UNCHECKED_CAST") - when (instance) { - is Cat -> CatSerializer as SerializationStrategy - is Dog -> DogSerializer as SerializationStrategy - else -> null - } - } -} - -object CatSerializer : SerializationStrategy { - override val descriptor = buildClassSerialDescriptor("Cat") { - element("catType") - } - - override fun serialize(encoder: Encoder, value: Cat) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.catType) - } - } -} - -object DogSerializer : SerializationStrategy { - override val descriptor = buildClassSerialDescriptor("Dog") { - element("dogType") - } - - override fun serialize(encoder: Encoder, value: Dog) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.dogType) - } - } -} -``` - -Using this module we can now serialize instances of `Cat` and `Dog`. - -```kotlin -val format = Json { serializersModule = module } - -fun main() { - println(format.encodeToString(AnimalProvider.createCat())) -} -``` - -> You can get the full code [here](../../guide/example/example-poly-20.kt) - -```text -{"type":"Cat","catType":"Tabby"} -``` - - - - ---- - -The next chapter covers [JSON features](json.md). - - - - -[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html -[PolymorphicSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic-serializer/index.html -[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html -[Polymorphic]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic/index.html -[DeserializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html -[SerializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html - - - -[SerializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html -[SerializersModule()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html -[_polymorphic]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/polymorphic.html -[subclass]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html -[plus]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/plus.html -[SerializersModuleBuilder.include]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/include.html -[PolymorphicModuleBuilder.defaultDeserializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default-deserializer.html -[PolymorphicModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/index.html -[SerializersModuleBuilder.polymorphicDefaultSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/polymorphic-default-serializer.html -[SerializersModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html - - - - -[Json.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html -[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html - - - diff --git a/docs/topics/serialization-customization-options.md b/docs/topics/serialization-customization-options.md index 2e346cbe75..e8a0af6e23 100644 --- a/docs/topics/serialization-customization-options.md +++ b/docs/topics/serialization-customization-options.md @@ -1,5 +1,5 @@ - [//]: # (title: Serialize classes) + The [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in Kotlin enables the serialization of all properties in classes defined by the primary constructor. You can further customize this behavior to fit your specific needs. @@ -399,7 +399,7 @@ fun main() { ### Validate data in primary constructor When you need to validate a constructor parameter before storing it in a property, -replace the parameter with a property in the primary constructor and perform validation in an `init { ... }` block. +replace the parameter with a property in the primary constructor and perform validation in an `init` block. This ensures the class is serializable and that invalid data cannot be deserialized: ```kotlin diff --git a/docs/topics/serialization-get-started-create.md b/docs/topics/serialization-get-started-create.md deleted file mode 100644 index e4b55a6123..0000000000 --- a/docs/topics/serialization-get-started-create.md +++ /dev/null @@ -1,170 +0,0 @@ -[//]: # (title: Add Kotlin serialization plugins and dependencies) - - -

This is the first part of the Getting started with Kotlin serialization tutorial:

-

First step Add Kotlin serialization plugins and dependencies
- Second step Serialize an object
- Third step Add dependencies to a Kotlin Notebook
- Fourth step Share a Kotlin Notebook
-

-
- -In this section of the tutorial, you will learn how to add the necessary serialization dependencies using IntelliJ IDEA. - -To get started, first download and install the latest version of [IntelliJ IDEA](https://www.jetbrains.com/idea/download/). - -## Create a project - -1. In IntelliJ IDEA, select **File | New | Project**. -2. In the panel on the left, select **New Project**. -3. Name the new project and change its location if necessary. - - > Select the **Create Git repository** checkbox to place the new project under version control. - > You will be able to do it later at any time. - > - {type="tip"} - -4. From the **Language** list, select **Kotlin**. -5. Select the **IntelliJ** build system. -6. From the **JDK list**, select the [JDK](https://www.oracle.com/java/technologies/downloads/) that you want to use in your project. -7. Enable the **Add sample code** option to create a file with a sample `"Hello World!"` application. - - > You can also enable the **Generate code with onboarding tips** option to add some additional useful comments to your sample code. - > - {type="tip"} - -8. Click **Create**. - -## Add plugins and dependencies for Kotlin serialization - -Before starting, you must configure your build script to use Kotlin serialization tools in your project. -The instructions below cover how to apply the Kotlin serialization plugin and add the necessary dependencies using Gradle -(with Kotlin DSL and Groovy DSL) and Maven. - - - - -1. Add the Kotlin serialization Gradle plugin `kotlin("plugin.serialization")` to your `build.gradle.kts` file: - - ```kotlin - plugins { - kotlin("jvm") version "%kotlinVersion%" - kotlin("plugin.serialization") version "%kotlinVersion%" - } - ``` - -2. Add the JSON serialization library dependency `org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%` to your `build.gradle.kts` file: - - ```kotlin - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%") - } - ``` - - - - - -1. Add the Kotlin serialization Gradle plugin `org.jetbrains.kotlin.plugin.serialization` to your `build.gradle` file: - - ```groovy - plugins { - id 'org.jetbrains.kotlin.jvm' version '%kotlinVersion%' - id 'org.jetbrains.kotlin.plugin.serialization' version '%kotlinVersion%' - } - ``` - -2. Add the JSON serialization library dependency `org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%` to your `build.gradle` file: - - ```groovy - dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%' - } - ``` - - - - - -1. Specify the Kotlin and serialization versions by adding the following properties to your `pom.xml` file: - - ```xml - - %kotlinVersion% - %serializationVersion% - - ``` - -2. Add the Kotlin serialization Maven plugin to the `` section of your `pom.xml` file: - - ```xml - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - compile - - - - - - kotlinx-serialization - - - - - org.jetbrains.kotlin - kotlin-maven-serialization - ${kotlin.version} - - - - - - ``` - -3. Add the JSON serialization library dependency to the `` section of your `pom.xml` file: - - ```xml - - - org.jetbrains.kotlinx - kotlinx-serialization-json - ${serialization.version} - - - ``` - - - - - -To set up the Kotlin compiler plugin for Bazel, see the example provided in the [rules_kotlin repository](https://github.com/bazelbuild/rules_kotlin/tree/master/examples/plugin/src/serialization). - - - - - -### Add Kotlin serialization dependencies for multiplatform projects - -You can use Kotlin serialization in multiplatform projects, including Kotlin/JS and Kotlin/Native. -To do so, you must add the JSON serialization library dependency to your common source set: - -```kotlin -commonMain { - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:%serializationVersion%") - } -} -``` - -### Optimizing Kotlin Serialization for Android with ProGuard and R8 - -THIS SHOULD BE PLACED IN AN ADVANCED SECTION FOR ANDROID \ No newline at end of file diff --git a/docs/topics/serialization-get-started-overview.md b/docs/topics/serialization-get-started-overview.md deleted file mode 100644 index 2a40efd0a6..0000000000 --- a/docs/topics/serialization-get-started-overview.md +++ /dev/null @@ -1,19 +0,0 @@ -[//]: # (title: Get started with Kotlin serialization) - -Serialization in Kotlin involves converting objects to a format that can be easily stored or transmitted and later reconstructed. - -Follow these steps to get started with Kotlin serialization: - -![First step](icon-1.svg){width=25}{type="joined"} [Create a project for Kotlin serialization](serialization-get-started-create.md) - -![Second step](icon-2.svg){width=25}{type="joined"} [Serialize an object to JSON]() - -![Third step](icon-3.svg){width=25}{type="joined"} [Deserialize a JSON string]() - -![Fourth step](icon-4.svg){width=25}{type="joined"} [Deserialize a JSON string back to an object]() - -## Next step - -Start by setting up an environment. - -[Proceed to the next part]() \ No newline at end of file diff --git a/docs/topics/serialization-get-started-serialize.md b/docs/topics/serialization-get-started-serialize.md deleted file mode 100644 index 0276e40166..0000000000 --- a/docs/topics/serialization-get-started-serialize.md +++ /dev/null @@ -1,49 +0,0 @@ -[//]: # (title: Serialize an object) - - -

This is the first part of the Getting started with Kotlin serialization tutorial:

-

First step Add Kotlin serialization plugins and dependencies
- Second step Serialize an object
- Third step Add dependencies to a Kotlin Notebook
- Fourth step Share a Kotlin Notebook
-

-
- -1. - -2. Make a class serializable by annotating it with `@Serializable`. - - ```kotlin - import kotlinx.serialization.Serializable - - @Serializable - data class Data(val a: Int, val b: String) - ``` - -3. Serialize an instance of this class by calling `Json.encodeToString()`. - - ```kotlin - import kotlinx.serialization.Serializable - import kotlinx.serialization.json.Json - import kotlinx.serialization.encodeToString - - @Serializable - data class Data(val a: Int, val b: String) - - fun main() { - val json = Json.encodeToString(Data(42, "str")) - } - ``` - - As a result, you get a string containing the state of this object in the JSON format: `{"a": 42, "b": "str"}` - - > You can also serialize object collections, such as lists, in a single call: - > - > ```kotlin - > val dataList = listOf(Data(42, "str"), Data(12, "test")) - > val jsonList = Json.encodeToString(dataList) - > ``` - > - {type="note"} - -4. \ No newline at end of file diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md index 1fc7eb1ac1..38c3a50141 100644 --- a/docs/topics/serialization-get-started.md +++ b/docs/topics/serialization-get-started.md @@ -135,21 +135,15 @@ commonMain { } ``` -## Serialize an object to JSON +## Serialize objects to JSON Serialization is the process of converting an object into a format that can be easily stored or transmitted, such as JSON. In Kotlin, you can serialize objects to JSON using the `kotlinx.serialization` library. -To make a class serializable, you need to use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation. +To make a class serializable, you need to mark it with the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation. This annotation indicates to the compiler to generate the necessary code for serializing and deserializing instances of the class. For more information, see [The @Serialization annotation](serialization.md#the-serializable-annotation) section. -When marking a class with the `@Serializable` annotation, consider the following: - -* Only properties that store their values directly, known as backing fields are serialized. Properties that compute their values dynamically using custom getters are not serialized. -* All parameters in the primary constructor must be properties of the object. -* If data validation is needed before serialization, you can use the init block to validate the properties. - Let's look at an example: 1. Import the necessary serialization libraries: @@ -159,79 +153,54 @@ Let's look at an example: import kotlinx.serialization.json.* ``` -2. Make a class serializable by annotating it with `@Serializable`. +2. Make a class serializable by annotating it with `@Serializable`: ```kotlin @Serializable data class Data(val a: Int, val b: String) ``` - > The `@Serialization` annotation enables default serialization of all properties in the primary constructor. - > You can adjust this behavior using various techniques. These include serialization of backing fields, defining - > custom constructors, specifying optional properties, marking properties as required and more. - > These techniques allow for precise control over which properties are serialized and how the serialization process is managed. + > The `@Serializable` annotation enables default serialization of all properties in the primary constructor. + > You can customize serialization behavior using various techniques like custom constructors, optional properties, and more. > For more information, see [Serialization customization options](serialization-customization-options.md). > {type="note"} -3. Serialize an instance of this class by calling `Json.encodeToString()`. +3. Serialize an instance of this class by calling the `Json.encodeToString()` function: ```kotlin - // Imports the necessary libraries + // Imports the necessary libraries for serialization and JSON handling import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString + // Marks the Data class as serializable @Serializable data class Data(val a: Int, val b: String) fun main() { - val json = Json.encodeToString(Data(42, "str")) + // Serializes an instance of the Data class into a JSON string + val json = Json.encodeToString(Data(42, "str")) + println(json) + // {"a":42,"b":"str"} } ``` + {kotlin-runnable="true"} As a result, you get a string containing the state of this object in the JSON format: `{"a": 42, "b": "str"}` - > You can serialize object collections, such as lists, in a single call: - > - > ```kotlin + > You can also serialize a collection of objects in a single call: + > + > ```kotlin > val dataList = listOf(Data(42, "str"), Data(12, "test")) > val jsonList = Json.encodeToString(dataList) > ``` - > + > {type="note"} -4. You can also use the `.serializer()` function to retrieve and use the automatically generated serializer: - - ```kotlin - // Imports the necessary libraries for serialization - import kotlinx.serialization.Serializable - import kotlinx.serialization.json.Json - import kotlinx.serialization.encodeToString - // Imports the KSerializer interface - import kotlinx.serialization.KSerializer - - // Marks the Data class as serializable - @Serializable - data class Data(val a: Int, val b: String) - - fun main() { - // Retrieves the automatically generated serializer for the Data class - val serializer: KSerializer = Data.serializer() - - // Serializes an instance of Data using the retrieved serializer - val json = Json.encodeToString(serializer, Data(42, "str")) - println(json) - // {"a":42,"b":"str"} - } - ``` - The `.serializer()` function allows you to explicitly obtain the serializer instance created by the `kotlinx.serialization` library for your `@Serializable` class, - allowing you to interact with the serialization process directly. - For more information, see the [Create custom serializers](create-custom-serializers.md) page. - -## Deserialize an object from JSON +## Deserialize objects from JSON -Deserialization reconstructs an object from its serialized form. +Deserialization converts a JSON string back into an object. To deserialize an object from JSON in Kotlin: @@ -271,6 +240,6 @@ To deserialize an object from JSON in Kotlin: ## What's next? -* Discover various techniques for adjusting serialization behavior in [Serialization customization options](serialization-customization-options.md). -* Learn how to create custom serializers in [Create custom serializers](create-custom-serializers.md). -* \ No newline at end of file +* Learn how to serialize standard types, including built-in types like numbers and strings, in [Serialize built-in types](serialization-serialize-builtin-types.md). +* Discover how to customize class serialization and adjust the default behavior of the `@Serializable` annotation in the [Serialize classes](serialization-customization-options.md) section. +* Dive deeper into handling JSON data and configuring JSON serialization in the [JSON serialization overview](configure-json-serialization.md). diff --git a/docs/topics/serialization-guide.md b/docs/topics/serialization-guide.md deleted file mode 100644 index 959996a2f1..0000000000 --- a/docs/topics/serialization-guide.md +++ /dev/null @@ -1,146 +0,0 @@ -# Kotlin Serialization Guide - -Kotlin Serialization is a cross-platform and multi-format framework for data serialization—converting -trees of objects to strings, byte arrays, or other _serial_ representations and back. -Kotlin Serialization fully supports and enforces the Kotlin type system, making sure only valid -objects can be deserialized. - -Kotlin Serialization is not just a library. It is a compiler plugin that is bundled with the Kotlin -compiler distribution itself. Build configuration is explained in [README.md](../../README.md#setup). -Once the project is set up, we can start serializing some classes. - -## Table of contents - -**Chapter 1.** [Basic Serialization](basic-serialization.md) (**start reading here**) - -* [Basics](basic-serialization.md#basics) - * [JSON encoding](basic-serialization.md#json-encoding) - * [JSON decoding](basic-serialization.md#json-decoding) -* [Serializable classes](basic-serialization.md#serializable-classes) - * [Backing fields are serialized](basic-serialization.md#backing-fields-are-serialized) - * [Constructor properties requirement](basic-serialization.md#constructor-properties-requirement) - * [Data validation](basic-serialization.md#data-validation) - * [Optional properties](basic-serialization.md#optional-properties) - * [Optional property initializer call](basic-serialization.md#optional-property-initializer-call) - * [Required properties](basic-serialization.md#required-properties) - * [Transient properties](basic-serialization.md#transient-properties) - * [Defaults are not encoded by default](basic-serialization.md#defaults-are-not-encoded-by-default) - * [Nullable properties](basic-serialization.md#nullable-properties) - * [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) - * [Referenced objects](basic-serialization.md#referenced-objects) - * [No compression of repeated references](basic-serialization.md#no-compression-of-repeated-references) - * [Generic classes](basic-serialization.md#generic-classes) - * [Serial field names](basic-serialization.md#serial-field-names) - - -**Chapter 2.** [Builtin Classes](builtin-classes.md) - - -* [Primitives](builtin-classes.md#primitives) - * [Numbers](builtin-classes.md#numbers) - * [Long numbers](builtin-classes.md#long-numbers) - * [Long numbers as strings](builtin-classes.md#long-numbers-as-strings) - * [Enum classes](builtin-classes.md#enum-classes) - * [Serial names of enum entries](builtin-classes.md#serial-names-of-enum-entries) -* [Composites](builtin-classes.md#composites) - * [Pair and triple](builtin-classes.md#pair-and-triple) - * [Lists](builtin-classes.md#lists) - * [Sets and other collections](builtin-classes.md#sets-and-other-collections) - * [Deserializing collections](builtin-classes.md#deserializing-collections) - * [Maps](builtin-classes.md#maps) - * [Unit and singleton objects](builtin-classes.md#unit-and-singleton-objects) - * [Duration](builtin-classes.md#duration) -* [Nothing](builtin-classes.md#nothing) - - -**Chapter 3.** [Serializers](serializers.md) - - -* [Introduction to serializers](serializers.md#introduction-to-serializers) - * [Plugin-generated serializer](serializers.md#plugin-generated-serializer) - * [Plugin-generated generic serializer](serializers.md#plugin-generated-generic-serializer) - * [Builtin primitive serializers](serializers.md#builtin-primitive-serializers) - * [Constructing collection serializers](serializers.md#constructing-collection-serializers) - * [Using top-level serializer function](serializers.md#using-top-level-serializer-function) -* [Custom serializers](serializers.md#custom-serializers) - * [Primitive serializer](serializers.md#primitive-serializer) - * [Delegating serializers](serializers.md#delegating-serializers) - * [Composite serializer via surrogate](serializers.md#composite-serializer-via-surrogate) - * [Hand-written composite serializer](serializers.md#hand-written-composite-serializer) - * [Sequential decoding protocol (experimental)](serializers.md#sequential-decoding-protocol-experimental) - * [Serializing 3rd party classes](serializers.md#serializing-3rd-party-classes) - * [Passing a serializer manually](serializers.md#passing-a-serializer-manually) - * [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) - * [Specifying serializer for a particular type](serializers.md#specifying-serializer-for-a-particular-type) - * [Specifying serializers for a file](serializers.md#specifying-serializers-for-a-file) - * [Specifying serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias) - * [Custom serializers for a generic type](serializers.md#custom-serializers-for-a-generic-type) - * [Format-specific serializers](serializers.md#format-specific-serializers) -* [Contextual serialization](serializers.md#contextual-serialization) - * [Serializers module](serializers.md#serializers-module) - * [Contextual serialization and generic classes](serializers.md#contextual-serialization-and-generic-classes) -* [Deriving external serializer for another Kotlin class (experimental)](serializers.md#deriving-external-serializer-for-another-kotlin-class-experimental) - * [External serialization uses properties](serializers.md#external-serialization-uses-properties) - - -**Chapter 4.** [Polymorphism](polymorphism.md) - - -* [Closed polymorphism](polymorphism.md#closed-polymorphism) - * [Static types](polymorphism.md#static-types) - * [Designing serializable hierarchy](polymorphism.md#designing-serializable-hierarchy) - * [Sealed classes](polymorphism.md#sealed-classes) - * [Custom subclass serial name](polymorphism.md#custom-subclass-serial-name) - * [Concrete properties in a base class](polymorphism.md#concrete-properties-in-a-base-class) - * [Objects](polymorphism.md#objects) -* [Open polymorphism](polymorphism.md#open-polymorphism) - * [Registered subclasses](polymorphism.md#registered-subclasses) - * [Serializing interfaces](polymorphism.md#serializing-interfaces) - * [Property of an interface type](polymorphism.md#property-of-an-interface-type) - * [Static parent type lookup for polymorphism](polymorphism.md#static-parent-type-lookup-for-polymorphism) - * [Explicitly marking polymorphic class properties](polymorphism.md#explicitly-marking-polymorphic-class-properties) - * [Registering multiple superclasses](polymorphism.md#registering-multiple-superclasses) - * [Polymorphism and generic classes](polymorphism.md#polymorphism-and-generic-classes) - * [Merging library serializers modules](polymorphism.md#merging-library-serializers-modules) - * [Default polymorphic type handler for deserialization](polymorphism.md#default-polymorphic-type-handler-for-deserialization) - * [Default polymorphic type handler for serialization](polymorphism.md#default-polymorphic-type-handler-for-serialization) - - -**Chapter 5.** [JSON Features](json.md) - - - - -**Chapter 6.** [Alternative and custom formats (experimental)](formats.md) - - -* [CBOR (experimental)](formats.md#cbor-experimental) - * [Ignoring unknown keys](formats.md#ignoring-unknown-keys) - * [Byte arrays and CBOR data types](formats.md#byte-arrays-and-cbor-data-types) -* [ProtoBuf (experimental)](formats.md#protobuf-experimental) - * [Field numbers](formats.md#field-numbers) - * [Integer types](formats.md#integer-types) - * [Lists as repeated fields](formats.md#lists-as-repeated-fields) - * [Packed fields](formats.md#packed-fields) - * [Oneof field (experimental)](formats.md#oneof-field-experimental) - * [Usage](formats.md#usage) - * [Alternative](formats.md#alternative) - * [ProtoBuf schema generator (experimental)](formats.md#protobuf-schema-generator-experimental) -* [Properties (experimental)](formats.md#properties-experimental) -* [Custom formats (experimental)](formats.md#custom-formats-experimental) - * [Basic encoder](formats.md#basic-encoder) - * [Basic decoder](formats.md#basic-decoder) - * [Sequential decoding](formats.md#sequential-decoding) - * [Adding collection support](formats.md#adding-collection-support) - * [Adding null support](formats.md#adding-null-support) - * [Efficient binary format](formats.md#efficient-binary-format) - * [Format-specific types](formats.md#format-specific-types) - - -**Appendix A.** [Serialization and value classes (IR-only)](value-classes.md) - - -* [Serializable value classes](value-classes.md#serializable-value-classes) -* [Unsigned types support (JSON only)](value-classes.md#unsigned-types-support-json-only) -* [Using value classes in your custom serializers](value-classes.md#using-value-classes-in-your-custom-serializers) - diff --git a/docs/topics/serialization-serialize-builtin-types.md b/docs/topics/serialization-serialize-builtin-types.md index 2188a69148..e301696cfa 100644 --- a/docs/topics/serialization-serialize-builtin-types.md +++ b/docs/topics/serialization-serialize-builtin-types.md @@ -1,29 +1,60 @@ [//]: # (title: Serialize built-in types) -The `kotlinx.serialization` library provides support for various built-in types, including primitives, composite types, -and some standard library classes. -The following sections describe the various types that can be serialized and provide examples to demonstrate their usage. + + +The `kotlinx.serialization` library supports various built-in types, including primitives, composite types, and some standard library classes. +The following sections describe these types in more detail and provide examples of how to serialize them. ## Primitive types Kotlin serialization supports the following primitive types: `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `Char`, `String`, and `enums`. -For example, here’s how a `Long` is serialized: +For example, here’s how you can serialize a `Long` type: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import kotlin.math.* + +//sampleStart +@Serializable +class Data(val signature: Long) + +fun main() { + val data = Data(0x1CAFE2FEED0BABE0) + println(Json.encodeToString(data)) + // {"signature":2067120338512882656} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + ### Numbers -You can serialize all types of integer and floating-point Kotlin numbers. +You can serialize all Kotlin number types, including integers and floating-point numbers, using their natural JSON representations. For example: - -```kotlin +//sampleStart @Serializable class Data( val answer: Int, @@ -35,11 +66,11 @@ fun main() { println(Json.encodeToString(data)) // {"answer":42,"pi":3.141592653589793} } -``` - - +//sampleEnd +``` +{kotlin-runnable="true"} -Their natural representation in JSON is used. + + +### Long numbers as strings + +When you serialize Kotlin `Long` values to JSON, JavaScript's native number type cannot represent the full range of a Kotlin `Long` type, +leading to precision loss. + +Kotlin/JS handles these large `Long` numbers correctly, but JavaScript's native methods don't. +A common workaround is to represent long numbers with full precision using the JSON string type. +Kotlin Serialization supports this approach with [`LongAsStringSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-long-as-string-serializer/), +which you can apply to a `Long` property using the `@Serializable` annotation: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import kotlin.math.* + +//sampleStart +@Serializable +class Data( + @Serializable(with=LongAsStringSerializer::class) + val signature: Long +) + +fun main() { + val data = Data(0x1CAFE2FEED0BABE0) + println(Json.encodeToString(data)) + // {"signature":"2067120338512882656"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + +> You can also specify a serializer like `LongAsStringSerializer` for all properties in a file. +> For more information, see the [Specify serializers for a file](third-party-classes.md#specify-serializers-for-a-file) section for more details. +> +{type="tip"} + + + +### Enum classes + +All enum classes are serializable by default requiring the `@Serializable` annotation. +When serialized in JSON, an `enum` is encoded as a string: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +// The @Serializable annotation is not needed for enum classes +enum class Status { SUPPORTED } + +@Serializable +class Project(val name: String, val status: Status) + +fun main() { + val data = Project("kotlinx.serialization", Status.SUPPORTED) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","status":"SUPPORTED"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + +> On Kotlin/JS and Kotlin/Native, you must use the `@Serializable` annotation for an enum class if you want to use as a root object, +> such as in `encodeToString(Status.SUPPORTED)`. +> +{type="note"} + + + +#### Customize serial names of enum entries + +> For more information on customizing serial names, see the [Customize serial names](serialization-customization-options.md#customize-serial-names) section. +> +{type="tip"} + +To customize the serial names of enum entries, apply the `@SerialName` annotation to the entries and annotate the entire enum class with `@Serializable`: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +// Requires the @Serializable annotation because of @SerialName +@Serializable +enum class Status { @SerialName("maintained") SUPPORTED } + +@Serializable +class Project(val name: String, val status: Status) + +fun main() { + val data = Project("kotlinx.serialization", Status.SUPPORTED) + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","status":"maintained"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +## Composite types + +Kotlin Serialization supports several composite types from the standard library, but not all classes are serializable. +For example, ranges and the [`Regex`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/) class are currently not supported. + +### Pair and triple + +You can serialize the [`Pair`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/) and [`Triple`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/) classes from the Kotlin standard library: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +class Project(val name: String) + +fun main() { + val pair = 1 to Project("kotlinx.serialization") + println(Json.encodeToString(pair)) + // {"first":1,"second":{"name":"kotlinx.serialization"}} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +### Collections + +Kotlin Serialization supports various collection types, including [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/), [`Set`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/), and [`Map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/). +Lists and sets are serialized as JSON arrays, while maps are represented as JSON objects. + +#### Serialize lists + +You can serialize a [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/) of serializable classes, which is represented as an array in JSON: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +class Project(val name: String) + +fun main() { + val list = listOf( + Project("kotlinx.serialization"), + Project("kotlinx.coroutines") + ) + println(Json.encodeToString(list)) + // [{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}] +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +#### Serialize sets + +[`Set`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/) is also represented as a JSON array: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +class Project(val name: String) + +fun main() { + val set = setOf( + Project("kotlinx.serialization"), + Project("kotlinx.coroutines") + ) + println(Json.encodeToString(set)) + // [{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}] +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +#### Serialize maps + +You can serialize a [`Map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/) with primitive or enum keys and any serializable values: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +class Project(val name: String) + +fun main() { + val map = mapOf( + 1 to Project("kotlinx.serialization"), + 2 to Project("kotlinx.coroutines") + ) + println(Json.encodeToString(map)) + // {"1":{"name":"kotlinx.serialization"},"2":{"name":"kotlinx.coroutines"}} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + +In JSON, Kotlin maps are represented as objects. Since JSON object keys are always strings, keys are encoded as strings, even if they are numbers in Kotlin. + +> JSON doesn't natively support complex or composite keys. +> To work around this and use structured objects as map keys, see the [Encode structured map keys](serialization-json-configuration.md#encode-structured-map-keys) section. +> +{type="note"} + + + + + +#### Deserialization behavior of collections + +When deserializing collections, the type specified in the code determines how the JSON data is interpreted. +For example, `List` allows duplicates, while `Set` automatically removes them: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +data class Data( + val a: List, + val b: Set +) + +fun main() { + val data = Json.decodeFromString(""" + { + "a": [42, 42], + "b": [42, 42] + } + """) + // No duplicate values in data.b property, because it is a Set + println(data) + // Data(a=[42, 42], b=[42]) +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + +### Unit and singleton objects + +The Kotlin [`Unit`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/) type, along with other singleton objects, is serializable. +A [singleton]((object-declarations.md)) is a class with only one instance, meaning its state is defined by the object itself, not by external properties. +In JSON, singleton objects are serialized as empty structures: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +object SerializationVersion { + val libraryVersion: String = "1.0.0" +} + +fun main() { + println(Json.encodeToString(SerializationVersion)) + // {} + println(Json.encodeToString(Unit)) + // {} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + +While serializing singleton objects might seem unnecessary, +it is useful for closed polymorphic class serialization, as described in the [Serialize objects in sealed hierarchies](serialization-polymorphism.md#serialize-objects-in-sealed-hierarchies) section. + + + + + +### Duration + +Kotlin's [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) class is serialized as a string in the ISO-8601-2 format. +Since Kotlin `1.7.20`, you can serialize `Duration` the following way: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.time.* + +//sampleStart +fun main() { + val duration = 1000.toDuration(DurationUnit.SECONDS) + println(Json.encodeToString(duration)) + // "PT16M40S" +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + + + +## Nothing + +The [`Nothing`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html) class is serializable by default, +but since it has no instances, encoding or decoding its values causes an exception. +It is used when a type is required syntactically but isn't involved in serialization, such as in parameterized polymorphic base classes: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +sealed class ParametrizedParent { + @Serializable + data class ChildWithoutParameter(val value: Int) : ParametrizedParent() +} + +fun main() { + println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42))) + // {"value":42} +} +//sampleEnd +``` +{kotlin-runnable="true"} + + + + + + diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md index ceb677f8f1..315bdce607 100644 --- a/docs/topics/serialization-transform-json.md +++ b/docs/topics/serialization-transform-json.md @@ -35,13 +35,13 @@ Although the input can either be a single object or an array, the goal is to ens You can use the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation in the data model to specify a custom serializer for the `users: List` property: - -```kotlin +//sampleStart // Uses UserListSerializer to handle the serialization of the users property @Serializable data class Project( @@ -74,7 +74,9 @@ fun main() { """)) // Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) } +//sampleEnd ``` +{kotlin-runnable="true} Since this example focuses on deserialization, the `UserListSerializer` only overrides the `transformDeserialize()` function. The `JsonTransformingSerializer` constructor takes the original serializer @@ -97,6 +99,12 @@ You can use the `transformSerialize()` function to unwrap a single-element list during serialization: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable data class Project( val name: String, @@ -123,7 +131,9 @@ fun main() { println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","users":{"name":"kotlin"}} } +//sampleEnd ``` +{kotlin-runnable="true} @@ -148,6 +158,12 @@ pass the above `ProjectSerializer` to [Json.encodeToString] function as was show the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable class Project(val name: String, val language: String) @@ -162,12 +178,16 @@ object ProjectSerializer : JsonTransformingSerializer(Project.serialize fun main() { val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) // using plugin-generated serializer + // Uses the plugin-generated serializer + println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","language":"Kotlin"} - println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer + println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer // {"name":"kotlinx.serialization"} } +//sampleEnd ``` +{kotlin-runnable="true} + > When serializing an object directly, you need to explicitly pass the custom serializer to the `Json.encodeToString()` > function to ensure that the custom serialization logic is applied. For more information, see the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section. @@ -187,22 +207,24 @@ fun main() { ## Content-based polymorphic deserialization -In [polymorphic serialization](polymorphism.md), a _class discriminator_, a dedicated `"type"` property in the JSON, +In [polymorphic serialization](serialization-polymorphism.md), a _class discriminator_, a dedicated `"type"` property in the JSON, is usually included to determine which serializer should be used to deserialize the Kotlin class. When no class discriminator is present in the JSON input, you can use [`JsonContentPolymorphicSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/) to infer the type from the structure of the JSON. This serializer allows you to override the `selectDeserializer()` function to choose the correct deserializer based on the JSON content. > When you use this serializer, the appropriate deserializer is chosen at runtime. -> It can either come from the [registered](polymorphism.md#registered-subclasses) or the default serializer. +> It can either come from the [registered](serialization-polymorphism.md#serialize-closed-polymorphic-classes) or the default serializer. > {type="tip"} - +import kotlinx.serialization.json.* -```kotlin +//sampleStart @Serializable abstract class Project { abstract val name: String @@ -236,11 +258,12 @@ fun main() { println(Json.decodeFromString(ListSerializer(ProjectSerializer), string)) // [OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)] } +//sampleEnd ``` {kotlin-runnable="true"} This example manually selects the appropriate subclass without using plugin-generated code, -which is why the class does not need to be sealed, as recommended in the [Sealed classes](polymorphism.md#sealed-classes) section. +which is why the class does not need to be sealed, as recommended in the [Serialize closed polymorphic classes](serialization-polymorphism.md#serialize-closed-polymorphic-classes) section. @@ -253,40 +276,42 @@ which is why the class does not need to be sealed, as recommended in the [Sealed -## Under the hood (experimental) +## Implement custom serialization logic in JSON (experimental) -Although abstract serializers mentioned above can cover most of the cases, it is possible to implement similar machinery -manually, using only the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) class. -If tweaking the abstract methods `transformSerialize()`/`transformDeserialize()`/`selectDeserializer()` is not enough, -then altering `serialize()`/`deserialize()` is a way to go. +Although abstract serializers can handle most cases, you can manually implement custom serialization logic in JSON using the [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/) class. +If modifying the `transformSerialize()`, `transformDeserialize()`, or `selectDeserializer()` functions is insufficient, +you can adjust the `serialize()` and `deserialize()` functions directly. -Here are some useful things about custom serializers with [Json]: +When working with `Json`, consider the following: -* `Encoder` can be cast to `JsonEncoder`, and `Decoder` to `JsonDecoder`, if the current format is `Json`. -* `JsonDecoder` has the [`decodeJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html) function and `JsonEncoder` - has the [`encodeJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html) function, - which basically retrieve an element from and insert an element to a current position in the stream. -* Both `JsonDecoder` and `JsonEncoder` have the `json` property, - which returns `Json` instance with all settings that are currently in use. -* `Json` has the [`encodeToJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html) and [`decodeFromJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) functions. +* You can cast `Encoder` to `JsonEncoder` and `Decoder` to `JsonDecoder` when the format is `Json`. +* `JsonDecoder` provides the [`decodeJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html) function, and `JsonEncoder` offers the [`encodeJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html) function. +These functions allow retrieving and inserting JSON elements at specific points in the stream. +* Both `JsonDecoder` and `JsonEncoder` have a `json` property, which gives access to the current `Json` instance with its active settings. +* `Json` provides the [`encodeToJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html) and [`decodeFromJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) functions. -Given all that, it is possible to implement two-stage conversion `Decoder -> JsonElement -> value` or +Using these tools, you can implement two-stage conversion processes such as `Decoder -> JsonElement -> value` or `value -> JsonElement -> Encoder`. -For example, you can implement a fully custom serializer for the following `Response` class so that its +For example, you can implement a custom serializer for the following `Response` class so that its `Ok` subclass is represented directly, but the `Error` subclass is represented by an object with the error message: - -```kotlin +//sampleStart +// Defines a sealed class for API responses @Serializable(with = ResponseSerializer::class) sealed class Response { data class Ok(val data: T) : Response() data class Error(val message: String) : Response() } +// Implements custom serialization logic for Response class class ResponseSerializer(private val dataSerializer: KSerializer) : KSerializer> { override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) { element("Ok", dataSerializer.descriptor) @@ -295,9 +320,11 @@ class ResponseSerializer(private val dataSerializer: KSerializer) : KSeria }) } + // Deserializes Response from JSON override fun deserialize(decoder: Decoder): Response { // Decoder -> JsonDecoder - require(decoder is JsonDecoder) // this class can be decoded only by Json + // Ensures the decoder is a JsonDecoder + require(decoder is JsonDecoder) // JsonDecoder -> JsonElement val element = decoder.decodeJsonElement() // JsonElement -> value @@ -306,9 +333,11 @@ class ResponseSerializer(private val dataSerializer: KSerializer) : KSeria return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element)) } + // Serializes Response to JSON override fun serialize(encoder: Encoder, value: Response) { // Encoder -> JsonEncoder - require(encoder is JsonEncoder) // This class can be encoded only by Json + // Ensures the encoder is a JsonEncoder + require(encoder is JsonEncoder) // value -> JsonElement val element = when (value) { is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data) @@ -320,9 +349,7 @@ class ResponseSerializer(private val dataSerializer: KSerializer) : KSeria } ``` -Having this serializable `Response` implementation, you can take any serializable payload for its data -and serialize or deserialize the corresponding responses: -This gives you fine-grained control on the representation of the `Response` class in the JSON output: +This `Response` class works with any serializable data type, giving you precise control over how the class is represented in JSON output: ```kotlin @Serializable @@ -343,8 +370,6 @@ fun main() { - - diff --git a/docs/topics/serialization.md b/docs/topics/serialization.md index 329d9ef38c..1d4f80c045 100644 --- a/docs/topics/serialization.md +++ b/docs/topics/serialization.md @@ -1,12 +1,11 @@ [//]: # (title: Serialization) **Serialization** is the process of converting data used by an application to a format that can be transferred over a -network or stored in a database or a file. In turn, deserialization is the opposite process of reading data from an -external source and converting it into a runtime object. +network or stored in a database or a file. Deserialization is the opposite process of converting external data back into a runtime object. Together, they are essential to most applications that exchange data with third parties. -Some data serialization formats, such as [JSON](https://www.json.org/json-en.html) and [protocol buffers](https://protobuf.dev/) are particularly common. -Being language-neutral and platform-neutral, they enable data exchange between systems written in any modern language. +Some data serialization formats, such as [JSON](https://www.json.org/json-en.html) and [Protocol Buffers](https://protobuf.dev/) are particularly common. +Being language-neutral and platform-neutral, these formats enable data exchange between systems written in any modern language. To convert an object tree to a string or to a sequence of bytes, it must go through two mutually intertwined processes: @@ -19,39 +18,30 @@ This universal process varies depending on the object and is managed by a serial The reverse process involves parsing the input format, decoding the primitive values, and then deserializing the resulting stream into objects. -In Kotlin, data serialization tools are available in a separate component, kotlinx.serialization. -It consists of several parts: the `org.jetbrains.kotlin.plugin.serialization` Gradle plugin, runtime libraries, and compiler -plugins. - -Compiler plugins, `kotlinx-serialization-compiler-plugin` and `kotlinx-serialization-compiler-plugin-embeddable`, -are published directly to Maven Central. The second plugin is designed for working with the `kotlin-compiler-embeddable` -artifact, which is the default option for scripting artifacts. -Gradle adds compiler plugins to your projects as compiler arguments. - If you're new to serialization in Kotlin, we recommend starting with the [Get Started with Serialization](serialization-get-started.md) guide. This section provides a step-by-step guide to help you set up and use Kotlin serialization in your projects. By following these steps, you can quickly get up to speed with the basics before diving into more complex topics. -## Libraries +## Kotlin serialization libraries -`kotlinx.serialization` provides sets of libraries for all supported platforms: JVM, JavaScript, Native, and for various -serialization formats, such as JSON, CBOR, and protocol buffers. For the complete list of supported serialization, -see the [supported formats](#supported-formats) section. +The `kotlinx.serialization` library offers support for all platforms, including JVM, JavaScript, Native. +It works with various serialization formats, such as JSON, CBOR, and Protocol buffers. For the complete list of supported serialization, +see the [supported formats](#supported-serialization-formats) section. -All Kotlin serialization libraries belong to the `org.jetbrains.kotlinx:` group. -Their names start with `kotlinx-serialization-` and have suffixes that reflect the serialization format. +All Kotlin serialization libraries are part of the `org.jetbrains.kotlinx:` group, with names +starting with `kotlinx-serialization-` and suffixes that reflect the serialization format. For example: * `org.jetbrains.kotlinx:kotlinx-serialization-json` provides JSON serialization for Kotlin projects. * `org.jetbrains.kotlinx:kotlinx-serialization-cbor` provides CBOR serialization. -Platform-specific artifacts are handled automatically; you don't need to add them manually. -Use the same dependencies in JVM, JS, Native, and multiplatform projects. +Platform-specific dependencies are automatically managed, so you don’t need to add them manually. +Use the same dependencies for JVM, JavaScript, Native, and multiplatform projects. -Note that the `kotlinx.serialization` libraries use their own versioning structure, which doesn't match Kotlin's versioning. -Check out the releases on [GitHub](https://github.com/Kotlin/kotlinx.serialization/releases) to find the latest versions. +The `kotlinx.serialization` libraries follow their own versioning structure, independent of Kotlin. +You can check out the releases on [GitHub](https://github.com/Kotlin/kotlinx.serialization/releases) to find the latest versions. -## Supported formats +## Supported serialization formats `kotlinx.serialization` includes libraries for various serialization formats: @@ -61,45 +51,28 @@ Check out the releases on [GitHub](https://github.com/Kotlin/kotlinx.serializati * [Properties](https://en.wikipedia.org/wiki/.properties): [`kotlinx-serialization-properties`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#properties) * [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md): [`kotlinx-serialization-hocon`](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md#hocon) (only on JVM) -Note that all libraries except JSON serialization (`kotlinx-serialization-json`) are [Experimental](https://kotlinlang.org/docs/components-stability.html), which means their API can be changed without notice. +All libraries except JSON serialization (`kotlinx-serialization-json`) are [experimental](components-stability.md), which means their API can be changed without notice. +For more details about JSON serialization, see [JSON serialization overview](configure-json-serialization.md). There are also community-maintained libraries that support more serialization formats, such as [YAML](https://yaml.org/) or [Apache Avro](https://avro.apache.org/). -For more details about the available serialization formats, see [Serialization formats](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md). - -## Supported types +You can find out more about experimental serialization formats in [Alternative and custom serialization formats](alternative-serialization-formats.md). -Kotlin serialization supports the following built-in primitive types: +## Supported serialization types -* `Boolean` -* `Char` -* Integer types: `Byte`, `Short`, `Int`, and `Long` -* Floating-point types: `Float` and `Double` -* `String` -* `enum` - -In addition to primitives Kotlin serialization also supports a number of composite types: - -* Pair and Triple: The simple data classes [`Pair`]((https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/)) and [`Triple`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/) from the Kotlin standard library are serializable. -* Lists, Sets, and other collections: The [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/), [`Set`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/), and other [collection](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/) types can be serialized. -These types are represented as lists in formats like JSON. A JSON list can be deserialized into both a `List` and a `Set`, with the `Set` automatically removing duplicate values. -* Maps: A [`Map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/) with primitive or `enum` keys and arbitrary serializable values can be serialized. In JSON, object keys are always strings, so keys are encoded as strings even if they are numbers in Kotlin. Composite keys are not supported by JSON but can be used in other formats. -* Unit and singleton objects: The built-in [`Unit`]() type and singleton objects are serializable. Singletons are serialized as empty structures. -* Duration: Since Kotlin 1.7.20, the [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) class of the `kotlin.time` package is serializable. It is serialized as a string in the ISO-8601-2 format. For example, "PT16M40S" is 16 minutes and 40 seconds. -* Nothing: The [`Nothing`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html) type is also serializable. However, since there are no instances of this class, it is impossible to encode or decode its values. This serializer is used when syntactically some type is needed, but it is not actually used in serialization. +Kotlin serialization supports a variety of built-in types, including all primitive types and composite types from the Kotlin standard library like the `List` type. +For more information, see [Serialize built-in types](serialization-serialize-builtin-types.md). > Not all types from the Kotlin standard library are serializable. In particular, [ranges](ranges.md) and the [`Regex`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/) class are not serializable at the moment. > Support for their serialization may be added in the future. -> +> {type="note"} Additionally, classes annotated with `@Serializable` are fully supported for serialization, enabling the conversion of class instances to and from formats like JSON. -For more information, see [Serializable types](serialization-customization-options.md). - -For more details and examples about the supported built-in types, see [Serialization GitHub repository](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/builtin-classes.md). +For more information, see [Serialize classes](serialization-customization-options.md). ## What's next * Learn the basics of Kotlin serialization in the [Get started with serialization guide](serialization-get-started-overview.md). -* To explore more complex scenarios of JSON serialization, see [JSON serialization overview](configure-json-serialization.md). -* Dive into the [Serializable classes](serialization-customization-options.md) section to learn how to modify the default behavior of the `@Serializable` annotation. +* To explore more complex JSON serialization scenarios, see [JSON serialization overview](configure-json-serialization.md). +* Dive into the [Serialize classes](serialization-customization-options.md) section to learn how to serialize classes and how to modify the default behavior of the `@Serializable` annotation. diff --git a/docs/topics/serializers.md b/docs/topics/serializers.md deleted file mode 100644 index b86513e27d..0000000000 --- a/docs/topics/serializers.md +++ /dev/null @@ -1,1258 +0,0 @@ - - -# Serializers - -This is the third chapter of the [Kotlin Serialization Guide](serialization-guide.md). -In this chapter we'll take a look at serializers in more detail, and we'll see how custom serializers can be written. - -**Table of contents** - - - -* [Introduction to serializers](#introduction-to-serializers) - * [Plugin-generated serializer](#plugin-generated-serializer) - * [Plugin-generated generic serializer](#plugin-generated-generic-serializer) - * [Builtin primitive serializers](#builtin-primitive-serializers) - * [Constructing collection serializers](#constructing-collection-serializers) - * [Using top-level serializer function](#using-top-level-serializer-function) -* [Custom serializers](#custom-serializers) - * [Primitive serializer](#primitive-serializer) - * [Delegating serializers](#delegating-serializers) - * [Composite serializer via surrogate](#composite-serializer-via-surrogate) - * [Hand-written composite serializer](#hand-written-composite-serializer) - * [Sequential decoding protocol (experimental)](#sequential-decoding-protocol-experimental) - * [Serializing 3rd party classes](#serializing-3rd-party-classes) - * [Passing a serializer manually](#passing-a-serializer-manually) - * [Specifying serializer on a property](#specifying-serializer-on-a-property) - * [Specifying serializer for a particular type](#specifying-serializer-for-a-particular-type) - * [Specifying serializers for a file](#specifying-serializers-for-a-file) - * [Specifying serializer globally using typealias](#specifying-serializer-globally-using-typealias) - * [Custom serializers for a generic type](#custom-serializers-for-a-generic-type) - * [Format-specific serializers](#format-specific-serializers) -* [Contextual serialization](#contextual-serialization) - * [Serializers module](#serializers-module) - * [Contextual serialization and generic classes](#contextual-serialization-and-generic-classes) -* [Deriving external serializer for another Kotlin class (experimental)](#deriving-external-serializer-for-another-kotlin-class-experimental) - * [External serialization uses properties](#external-serialization-uses-properties) - - - -## Introduction to serializers - -Formats, like JSON, control the _encoding_ of an object into specific output bytes, but how the object is decomposed -into its constituent properties is controlled by a _serializer_. So far we've been using automatically-derived -serializers by using the [`@Serializable`][Serializable] annotation as explained in -the [Serializable classes](/docs/basic-serialization.md#serializable-classes) section, or using builtin serializers that were shown in -the [Builtin classes](/docs/builtin-classes.md) section. - -As a motivating example, let us take the following `Color` class with an integer value storing its `rgb` bytes. - - - -```kotlin -@Serializable -class Color(val rgb: Int) - -fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-01.kt). - -By default this class serializes its `rgb` property into JSON. - -```text -{"rgb":65280} -``` - - - -### Plugin-generated serializer - -Every class marked with the `@Serializable` annotation, like the `Color` class from the previous example, -gets an instance of the [KSerializer] interface automatically generated by the Kotlin Serialization compiler plugin. -We can retrieve this instance using the `.serializer()` function on the class's companion object. - -We can examine its [descriptor][KSerializer.descriptor] property that describes the structure of -the serialized class. We'll learn more details about that in the upcoming sections. - - - -```kotlin -fun main() { - val colorSerializer: KSerializer = Color.serializer() - println(colorSerializer.descriptor) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-02.kt). - -```text -Color(rgb: kotlin.Int) -``` - - - -This serializer is automatically retrieved and used by the Kotlin Serialization framework when the `Color` class -is itself serialized, or when it is used as a property of other classes. - -> You cannot define your own function `serializer()` on a companion object of a serializable class. - -### Plugin-generated generic serializer - -For generic classes, like the `Box` class shown in the [Generic classes](basic-serialization.md#generic-classes) section, -the automatically generated `.serializer()` function accepts as many parameters as there are type parameters in the -corresponding class. These parameters are of type [KSerializer], so the actual type argument's serializer has -to be provided when constructing an instance of a serializer for a generic class. - - - -```kotlin -@Serializable -@SerialName("Box") -class Box(val contents: T) - -fun main() { - val boxedColorSerializer = Box.serializer(Color.serializer()) - println(boxedColorSerializer.descriptor) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-03.kt). - -As we can see, a serializer was instantiated to serialize a concrete `Box`. - -```text -Box(contents: Color) -``` - - - -### Builtin primitive serializers - -The serializers for the [primitive builtin classes](builtin-classes.md#primitives) can be retrieved -using `.serializer()` extensions. - - - -```kotlin -fun main() { - val intSerializer: KSerializer = Int.serializer() - println(intSerializer.descriptor) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-04.kt). - - - -### Constructing collection serializers - -[Builtin collection serializers](builtin-classes.md#lists), when needed, must be explicitly constructed -using the corresponding functions [ListSerializer()], [SetSerializer()], [MapSerializer()], etc. -These classes are generic, so to instantiate their serializer we must provide the serializers for the -corresponding number of their type parameters. -For example, we can produce a serializer for a `List` in the following way. - - - -```kotlin -fun main() { - val stringListSerializer: KSerializer> = ListSerializer(String.serializer()) - println(stringListSerializer.descriptor) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-05.kt). - - - -### Using top-level serializer function - -When in doubt, you can always use the top-level generic `serializer()` -function to retrieve a serializer for an arbitrary Kotlin type in your source-code. - - - -```kotlin -@Serializable -@SerialName("Color") -class Color(val rgb: Int) - -fun main() { - val stringToColorMapSerializer: KSerializer> = serializer() - println(stringToColorMapSerializer.descriptor) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-06.kt). - - - -## Custom serializers - -A plugin-generated serializer is convenient, but it may not produce the JSON we want -for such a class as `Color`. Let's study alternatives. - -### Primitive serializer - -We want to serialize the `Color` class as a hex string with the green color represented as `"00ff00"`. -To achieve this, we write an object that implements the [KSerializer] interface for the `Color` class. - - - -```kotlin -object ColorAsStringSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: Color) { - val string = value.rgb.toString(16).padStart(6, '0') - encoder.encodeString(string) - } - - override fun deserialize(decoder: Decoder): Color { - val string = decoder.decodeString() - return Color(string.toInt(16)) - } -} -``` - -Serializer has three required pieces. - -* The [serialize][SerializationStrategy.serialize] function implements [SerializationStrategy]. - It receives an instance of [Encoder] and a value to serialize. - It uses the `encodeXxx` functions of `Encoder` to represent a value as a sequence of primitives. There is an - `encodeXxx` for each primitive type supported by serialization. - In our example, [encodeString][Encoder.encodeString] is used. - -* The [deserialize][DeserializationStrategy.deserialize] function implements [DeserializationStrategy]. - It receives an instance of [Decoder] and returns a - deserialized value. It uses the `decodeXxx` functions of `Decoder`, which mirror the corresponding functions of `Encoder`. - In our example [decodeString][Decoder.decodeString] is used. - -* The [descriptor][KSerializer.descriptor] property must faithfully explain what exactly the `encodeXxx` and `decodeXxx` - functions do so that a format implementation knows in advance what encoding/decoding methods they call. - Some formats might also use it to generate a schema for the serialized data. For primitive serialization, - the [PrimitiveSerialDescriptor][PrimitiveSerialDescriptor()] function must be used with a unique name of the - type that is being serialized. - [PrimitiveKind] describes the specific `encodeXxx`/`decodeXxx` method that is being used in the implementation. - -> When the `descriptor` does not correspond to the encoding/decoding methods, then the behavior of the resulting code -> is unspecified, and may arbitrarily change in future updates. - -The next step is to bind a serializer to a class. This is done with the [`@Serializable`][Serializable] annotation by adding -the [`with`][Serializable.with] property value. - -```kotlin -@Serializable(with = ColorAsStringSerializer::class) -class Color(val rgb: Int) -``` - -Now we can serialize the `Color` class as we did before. - -```kotlin -fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-07.kt). - -We get the serial representation as the hex string we wanted. - -```text -"00ff00" -``` - - - -Deserialization is also straightforward because we implemented the `deserialize` method. - - - -```kotlin -@Serializable(with = ColorAsStringSerializer::class) -class Color(val rgb: Int) - -fun main() { - val color = Json.decodeFromString("\"00ff00\"") - println(color.rgb) // prints 65280 -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-08.kt). - - - -It also works if we serialize or deserialize a different class with `Color` properties. - - - -```kotlin -@Serializable(with = ColorAsStringSerializer::class) -data class Color(val rgb: Int) - -@Serializable -data class Settings(val background: Color, val foreground: Color) - -fun main() { - val data = Settings(Color(0xffffff), Color(0)) - val string = Json.encodeToString(data) - println(string) - require(Json.decodeFromString(string) == data) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-09.kt). - -Both `Color` properties are serialized as strings. - -```text -{"background":"ffffff","foreground":"000000"} -``` - - - -### Delegating serializers - -In the previous example, we represented the `Color` class as a string. -String is considered to be a primitive type, therefore we used `PrimitiveClassDescriptor` and specialized `encodeString` method. -Now let's see what our actions would be if we have to serialize `Color` as another non-primitive type, let's say `IntArray`. - -An implementation of [KSerializer] for our original `Color` class is going to perform a conversion between -`Color` and `IntArray`, but delegate the actual serialization logic to `IntArraySerializer` -using [encodeSerializableValue][Encoder.encodeSerializableValue] and -[decodeSerializableValue][Decoder.decodeSerializableValue]. - -```kotlin -import kotlinx.serialization.builtins.IntArraySerializer - -class ColorIntArraySerializer : KSerializer { - private val delegateSerializer = IntArraySerializer() - override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor) - - override fun serialize(encoder: Encoder, value: Color) { - val data = intArrayOf( - (value.rgb shr 16) and 0xFF, - (value.rgb shr 8) and 0xFF, - value.rgb and 0xFF - ) - encoder.encodeSerializableValue(delegateSerializer, data) - } - - override fun deserialize(decoder: Decoder): Color { - val array = decoder.decodeSerializableValue(delegateSerializer) - return Color((array[0] shl 16) or (array[1] shl 8) or array[2]) - } -} -``` - -Note that we can't use default `Color.serializer().descriptor` here because formats that rely -on the schema may think that we would call `encodeInt` instead of `encodeSerializableValue`. -Neither we can use `IntArraySerializer().descriptor` directly — otherwise, formats that handle int arrays specially -can't tell if `value` is really a `IntArray` or a `Color`. Don't worry, this optimization would still kick in -when serializing actual underlying int array. - -> Example of how format can treat arrays specially is shown in the [formats guide](formats.md#format-specific-types). - -Now we can use the serializer: - -```kotlin -@Serializable(with = ColorIntArraySerializer::class) -class Color(val rgb: Int) - -fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} -``` - -As you can see, such array representation is not very useful in JSON, -but may save some space when used with a `ByteArray` and a binary format. - -> You can get the full code [here](../../guide/example/example-serializer-10.kt). - -```text -[0,255,0] -``` - - - - -### Composite serializer via surrogate - -Now our challenge is to get `Color` serialized so that it is represented in JSON as if it is a class -with three properties—`r`, `g`, and `b`—so that JSON encodes it as an object. -The easiest way to achieve this is to define a _surrogate_ class mimicking the serialized form of `Color` that -we are going to use for its serialization. We also set the [SerialName] of this surrogate class to `Color`. Then if -any format uses this name the surrogate looks like it is a `Color` class. -The surrogate class can be `private`, and can enforce all the constraints on the serial representation -of the class in its `init` block. - -```kotlin -@Serializable -@SerialName("Color") -private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { - init { - require(r in 0..255 && g in 0..255 && b in 0..255) - } -} -``` - -> An example of where the class name is used is shown in -> the [Custom subclass serial name](polymorphism.md#custom-subclass-serial-name) section in the chapter on polymorphism. - -Now we can use the `ColorSurrogate.serializer()` function to retrieve a plugin-generated serializer for the -surrogate class. - -We can use the same approach as in [delegating serializer](#delegating-serializers), but this time, -we are fully reusing an automatically -generated [SerialDescriptor] for the surrogate because it should be indistinguishable from the original. - -```kotlin -object ColorSerializer : KSerializer { - override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor - - override fun serialize(encoder: Encoder, value: Color) { - val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) - encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) - } - - override fun deserialize(decoder: Decoder): Color { - val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) - return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) - } -} -``` - -We bind the `ColorSerializer` serializer to the `Color` class. - -```kotlin -@Serializable(with = ColorSerializer::class) -class Color(val rgb: Int) -``` - -Now we can enjoy the result of serialization for the `Color` class. - - - -> You can get the full code [here](../../guide/example/example-serializer-11.kt). - -```text -{"r":0,"g":255,"b":0} -``` - - - -### Hand-written composite serializer - -There are some cases where a surrogate solution does not fit. Perhaps we want to avoid the performance -implications of additional allocation, or we want a configurable/dynamic set of properties for the -resulting serial representation. In these cases we need to manually write a class -serializer which mimics the behaviour of a generated serializer. - -```kotlin -object ColorAsObjectSerializer : KSerializer { -``` - -Let's introduce it piece by piece. First, a descriptor is defined using the [buildClassSerialDescriptor] builder. -The [element][ClassSerialDescriptorBuilder.element] function in the builder DSL automatically fetches serializers -for the corresponding fields by their type. The order of elements is important. They are indexed starting from zero. - -```kotlin - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Color") { - element("r") - element("g") - element("b") - } -``` - -> The "element" is a generic term here. What is an element of a descriptor depends on its [SerialKind]. -> Elements of a class descriptor are its properties, elements of a enum descriptor are its cases, etc. - -Then we write the `serialize` function using the [encodeStructure] DSL that provides access to -the [CompositeEncoder] in its block. The difference between [Encoder] and [CompositeEncoder] is the latter -has `encodeXxxElement` functions that correspond to the `encodeXxx` functions of the former. They must be called -in the same order as in the descriptor. - -```kotlin - override fun serialize(encoder: Encoder, value: Color) = - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) - encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) - encodeIntElement(descriptor, 2, value.rgb and 0xff) - } -``` - -The most complex piece of code is the `deserialize` function. It must support formats, like JSON, that -can decode properties in an arbitrary order. It starts with the call to [decodeStructure] to -get access to a [CompositeDecoder]. Inside it we write a loop that repeatedly calls -[decodeElementIndex][CompositeDecoder.decodeElementIndex] to decode the index of the next element, then we decode the corresponding -element using [decodeIntElement][CompositeDecoder.decodeIntElement] in our example, and finally we terminate the loop when -`CompositeDecoder.DECODE_DONE` is encountered. - -```kotlin - override fun deserialize(decoder: Decoder): Color = - decoder.decodeStructure(descriptor) { - var r = -1 - var g = -1 - var b = -1 - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> r = decodeIntElement(descriptor, 0) - 1 -> g = decodeIntElement(descriptor, 1) - 2 -> b = decodeIntElement(descriptor, 2) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - require(r in 0..255 && g in 0..255 && b in 0..255) - Color((r shl 16) or (g shl 8) or b) - } -``` - - - -Now we bind the resulting serializer to the `Color` class and test its serialization/deserialization. - -```kotlin -@Serializable(with = ColorAsObjectSerializer::class) -data class Color(val rgb: Int) - -fun main() { - val color = Color(0x00ff00) - val string = Json.encodeToString(color) - println(string) - require(Json.decodeFromString(string) == color) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-12.kt). - -As before, we got the `Color` class represented as a JSON object with three keys: - -```text -{"r":0,"g":255,"b":0} -``` - - - -### Sequential decoding protocol (experimental) - -The implementation of the `deserialize` function from the previous section works with any format. However, -some formats either always store all the complex data in order, or only do so sometimes (JSON always stores -collections in order). With these formats the complex protocol of calling `decodeElementIndex` in the loop is -not needed, and a faster implementation can be used if the [CompositeDecoder.decodeSequentially] function returns `true`. -The plugin-generated serializers are actually conceptually similar to the below code. - - - -```kotlin - override fun deserialize(decoder: Decoder): Color = - decoder.decodeStructure(descriptor) { - var r = -1 - var g = -1 - var b = -1 - if (decodeSequentially()) { // sequential decoding protocol - r = decodeIntElement(descriptor, 0) - g = decodeIntElement(descriptor, 1) - b = decodeIntElement(descriptor, 2) - } else while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> r = decodeIntElement(descriptor, 0) - 1 -> g = decodeIntElement(descriptor, 1) - 2 -> b = decodeIntElement(descriptor, 2) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - require(r in 0..255 && g in 0..255 && b in 0..255) - Color((r shl 16) or (g shl 8) or b) - } -``` - - - -> You can get the full code [here](../../guide/example/example-serializer-13.kt). - - - -### Serializing 3rd party classes - -Sometimes an application has to work with an external type that is not serializable. -Let us use [java.util.Date] as an example. As before, we start by writing an implementation of [KSerializer] -for the class. Our goal is to get a `Date` serialized as a long number of milliseconds following the -approach from the [Primitive serializer](#primitive-serializer) section. - -> In the following sections any kind of `Date` serializer would work. For example, if we want `Date` to be serialized -> as an object, we would use an approach from -> the [Composite serializer via surrogate](#composite-serializer-via-surrogate) section. -> See also [Deriving external serializer for another Kotlin class (experimental)](#deriving-external-serializer-for-another-kotlin-class-experimental) -> when you need to serialize a 3rd-party Kotlin class that could have been serializable, but is not. - - - -```kotlin -object DateAsLongSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) - override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) - override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) -} -``` - -We cannot bind the `DateAsLongSerializer` serializer to the `Date` class with the [`@Serializable`][Serializable] annotation -because we don't control the `Date` source code. There are several ways to work around that. - -### Passing a serializer manually - -All `encodeToXxx` and `decodeFromXxx` functions have an overload with the first serializer parameter. -When a non-serializable class, like `Date`, is the top-level class being serialized, we can use those. - -```kotlin -fun main() { - val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00") - println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-14.kt). - -```text -1455494400000 -``` - - - -### Specifying serializer on a property - -When a property of a non-serializable class, like `Date`, is serialized as part of a serializable class we must supply -its serializer or the code will not compile. This is accomplished using the [`@Serializable`][Serializable] annotation on the property. - - - -```kotlin -@Serializable -class ProgrammingLanguage( - val name: String, - @Serializable(with = DateAsLongSerializer::class) - val stableReleaseDate: Date -) - -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-15.kt). - -The `stableReleaseDate` property is serialized with the serialization strategy that we specified for it: - -```text -{"name":"Kotlin","stableReleaseDate":1455494400000} -``` - - - -### Specifying serializer for a particular type - -[`@Serializable`][Serializable] annotation can also be applied directly to the types. -This is handy when a class that requires a custom serializer, such as `Date`, happens to be a generic type argument. -The most common use case for that is when you have a list of dates: - - - -```kotlin -@Serializable -class ProgrammingLanguage( - val name: String, - val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> -) - -fun main() { - val df = SimpleDateFormat("yyyy-MM-ddX") - val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-16.kt). - -```text -{"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]} -``` - - - -### Specifying serializers for a file - -A serializer for a specific type, like `Date`, can be specified for a whole source code file with the file-level -[UseSerializers] annotation at the beginning of the file. - -```kotlin -@file:UseSerializers(DateAsLongSerializer::class) -``` - - - - - -Now a `Date` property can be used in a serializable class without additional annotations. - -```kotlin -@Serializable -class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) - -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(Json.encodeToString(data)) -} -``` -> You can get the full code [here](../../guide/example/example-serializer-17.kt). - -```text -{"name":"Kotlin","stableReleaseDate":1455494400000} -``` - - - -### Specifying serializer globally using typealias - -kotlinx.serialization tends to be the always-explicit framework when it comes to serialization strategies: normally, -they should be explicitly mentioned in `@Serializable` annotation. Therefore, we do not provide any kind of global serializer -configuration (except for [context serializer](#contextual-serialization) mentioned later). - -However, in projects with a large number of files and classes, it may be too cumbersome to specify `@file:UseSerializers` -every time, especially for classes like `Date` or `Instant` that have a fixed strategy of serialization across the project. -For such cases, it is possible to specify serializers using `typealias`es, as they preserve annotations, including serialization-related ones: - - -```kotlin -typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date - -typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date -``` - -Using these new different types, it is possible to serialize a Date differently without additional annotations: - -```kotlin -@Serializable -class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) - -fun main() { - val format = SimpleDateFormat("yyyy-MM-ddX") - val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) - println(Json.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-18.kt). - -```text -{"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000} -``` - - - -### Custom serializers for a generic type - -Let us take a look at the following example of the generic `Box` class. -It is marked with `@Serializable(with = BoxSerializer::class)` as we plan to have a custom serialization -strategy for it. - -```kotlin -@Serializable(with = BoxSerializer::class) -data class Box(val contents: T) -``` - -An implementation of [KSerializer] for a regular type is written as an `object`, as we saw in this chapter's -examples for the `Color` type. A generic class serializer is instantiated with serializers -for its generic parameters. We saw this in the [Plugin-generated generic serializer](#plugin-generated-generic-serializer) section. -A custom serializer for a generic class must be a `class` with a constructor that accepts as many [KSerializer] -parameters as the type has generic parameters. Let us write a `Box` serializer that erases itself during -serialization, delegating everything to the underlying serializer of its `data` property. - -```kotlin -class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer> { - override val descriptor: SerialDescriptor = dataSerializer.descriptor - override fun serialize(encoder: Encoder, value: Box) = dataSerializer.serialize(encoder, value.contents) - override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) -} -``` - -Now we can serialize and deserialize `Box`. - -```kotlin -@Serializable -data class Project(val name: String) - -fun main() { - val box = Box(Project("kotlinx.serialization")) - val string = Json.encodeToString(box) - println(string) - println(Json.decodeFromString>(string)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-19.kt). - -The resulting JSON looks like the `Project` class was serialized directly. - -```text -{"name":"kotlinx.serialization"} -Box(contents=Project(name=kotlinx.serialization)) -``` - - - -### Format-specific serializers - -The above custom serializers worked in the same way for every format. However, there might be format-specific -features that a serializer implementation would like to take advantage of. - -* The [Json transformations](json.md#json-transformations) section of the [Json](json.md) chapter provides examples - of serializers that utilize JSON-specific features. - -* A format implementation can have a format-specific representation for a type as explained - in the [Format-specific types](formats.md#format-specific-types) section of - the [Alternative and custom formats (experimental)](formats.md) chapter. - -This chapter proceeds with a generic approach to tweaking the serialization strategy based on the context. - -## Contextual serialization - -All the previous approaches to specifying custom serialization strategies were _static_, that is -fully defined at compile-time. The exception was the [Passing a serializer manually](#passing-a-serializer-manually) -approach, but it worked only on a top-level object. You might need to change the serialization -strategy for objects deep in the serialized object tree at run-time, with the strategy being selected in a context-dependent way. -For example, you might want to represent `java.util.Date` in JSON format as an ISO 8601 string or as a long integer -depending on a version of a protocol you are serializing data for. This is called _contextual_ serialization, and it -is supported by a built-in [ContextualSerializer] class. Usually we don't have to use this serializer class explicitly—there -is the [Contextual] annotation providing a shortcut to -the `@Serializable(with = ContextualSerializer::class)` annotation, -or the [UseContextualSerialization] annotation can be used at the file-level just like -the [UseSerializers] annotation. Let's see an example utilizing the former. - - - -```kotlin -@Serializable -class ProgrammingLanguage( - val name: String, - @Contextual - val stableReleaseDate: Date -) -``` - - - -To actually serialize this class we must provide the corresponding context when calling the `encodeToXxx`/`decodeFromXxx` -functions. Without it we'll get a "Serializer for class 'Date' is not found" exception. - -> See [here](../../guide/example/example-serializer-20.kt) for an example that produces that exception. - - - - - -### Serializers module - -To provide a context, we define a [SerializersModule] instance that describes which serializers shall be used -at run-time to serialize which contextually-serializable classes. This is done using the -[SerializersModule {}][SerializersModule()] builder function, which provides the [SerializersModuleBuilder] DSL to -register serializers. In the below example we use the [contextual][_contextual] function with the serializer. The corresponding -class this serializer is defined for is fetched automatically via the `reified` type parameter. - -```kotlin -private val module = SerializersModule { - contextual(DateAsLongSerializer) -} -``` - -Next we create an instance of the [Json] format with this module using the -[Json {}][Json()] builder function and the [serializersModule][JsonBuilder.serializersModule] property. - -> Details on custom JSON configurations can be found in -> the [JSON configuration](json.md#json-configuration) section. - -```kotlin -val format = Json { serializersModule = module } -``` - -Now we can serialize our data with this `format`. - -```kotlin -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(format.encodeToString(data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-21.kt). -```text -{"name":"Kotlin","stableReleaseDate":1455494400000} -``` - - - -### Contextual serialization and generic classes - -In the previous section we saw that we can register serializer instance in the module for a class we want to serialize contextually. -We also know that [serializers for generic classes have constructor parameters](#custom-serializers-for-a-generic-type) — type arguments serializers. -It means that we can't use one serializer instance for a class if this class is generic: - -```kotlin -val incorrectModule = SerializersModule { - // Can serialize only Box, but not Box or others - contextual(BoxSerializer(Int.serializer())) -} -``` - -For cases when one want to serialize contextually a generic class, it is possible to register provider in the module: - -```kotlin -val correctModule = SerializersModule { - // args[0] contains Int.serializer() or String.serializer(), depending on the usage - contextual(Box::class) { args -> BoxSerializer(args[0]) } -} -``` - - - -> Additional details on serialization modules are given in -> the [Merging library serializers modules](polymorphism.md#merging-library-serializers-modules) section of -> the [Polymorphism](polymorphism.md) chapter. - -## Deriving external serializer for another Kotlin class (experimental) - -If a 3rd-party class to be serialized is a Kotlin class with a properties-only primary constructor, a kind of -class which could have been made `@Serializable`, then you can generate an _external_ serializer for it -using the [Serializer] annotation on an object with the [`forClass`][Serializer.forClass] property. - -```kotlin -// NOT @Serializable -class Project(val name: String, val language: String) - -@Serializer(forClass = Project::class) -object ProjectSerializer -``` - -You must bind this serializer to a class using one of the approaches explained in this chapter. We'll -follow the [Passing a serializer manually](#passing-a-serializer-manually) approach for this example. - -```kotlin -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(ProjectSerializer, data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-22.kt). - -This gets all the `Project` properties serialized: - -```text -{"name":"kotlinx.serialization","language":"Kotlin"} -``` - - - -### External serialization uses properties - -As we saw earlier, the regular `@Serializable` annotation creates a serializer so that -[Backing fields are serialized](basic-serialization.md#backing-fields-are-serialized). _External_ serialization using -`Serializer(forClass = ...)` has no access to backing fields and works differently. -It serializes only _accessible_ properties that have setters or are part of the primary constructor. -The following example shows this. - -```kotlin -// NOT @Serializable, will use external serializer -class Project( - // val in a primary constructor -- serialized - val name: String -) { - var stars: Int = 0 // property with getter & setter -- serialized - - val path: String // getter only -- not serialized - get() = "kotlin/$name" - - private var locked: Boolean = false // private, not accessible -- not serialized -} - -@Serializer(forClass = Project::class) -object ProjectSerializer - -fun main() { - val data = Project("kotlinx.serialization").apply { stars = 9000 } - println(Json.encodeToString(ProjectSerializer, data)) -} -``` - -> You can get the full code [here](../../guide/example/example-serializer-23.kt). - -The output is shown below. - -```text -{"name":"kotlinx.serialization","stars":9000} -``` - - - ---- - -The next chapter covers [Polymorphism](polymorphism.md). - - -[java.util.Date]: https://docs.oracle.com/javase/8/docs/api/java/util/Date.html - - - - -[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html -[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html -[KSerializer.descriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/descriptor.html -[SerializationStrategy.serialize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/serialize.html -[SerializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html -[DeserializationStrategy.deserialize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/deserialize.html -[DeserializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html -[Serializable.with]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/with.html -[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html -[UseSerializers]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-serializers/index.html -[ContextualSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual-serializer/index.html -[Contextual]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual/index.html -[UseContextualSerialization]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-contextual-serialization/index.html -[Serializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/index.html -[Serializer.forClass]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/for-class.html - - - -[ListSerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-list-serializer.html -[SetSerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-set-serializer.html -[MapSerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-map-serializer.html - - - -[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html -[Encoder.encodeString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-string.html -[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html -[Decoder.decodeString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-string.html -[Encoder.encodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html -[Decoder.decodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html -[encodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html -[CompositeEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/index.html -[decodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/decode-structure.html -[CompositeDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html -[CompositeDecoder.decodeElementIndex]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html -[CompositeDecoder.decodeIntElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-int-element.html -[CompositeDecoder.decodeSequentially]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html - - - -[PrimitiveSerialDescriptor()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-serial-descriptor.html -[PrimitiveKind]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-kind/index.html -[SerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html -[buildClassSerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/build-class-serial-descriptor.html -[ClassSerialDescriptorBuilder.element]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/element.html -[SerialKind]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-kind/index.html - - - -[SerializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html -[SerializersModule()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html -[SerializersModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html -[_contextual]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/contextual.html - - - - -[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html -[Json()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html -[JsonBuilder.serializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/serializers-module.html - - - diff --git a/docs/topics/value-classes.md b/docs/topics/value-classes.md index fed90f6a2e..3cf43b9bdf 100644 --- a/docs/topics/value-classes.md +++ b/docs/topics/value-classes.md @@ -93,7 +93,7 @@ The output is following: ## Using value classes in your custom serializers Let's return to our `NamedColor` example and try to write a custom serializer for it. Normally, as shown -in [Hand-written composite serializer](serializers.md#hand-written-composite-serializer), we would write the following code +in [Hand-written composite serializer](create-custom-serializers.md#create-a-custom-composite-serializer), we would write the following code in `serialize` method: ```kotlin @@ -122,7 +122,7 @@ override fun serialize(encoder: Encoder, value: NamedColor) { The same principle goes also with [CompositeDecoder]: it has [decodeInlineElement][CompositeDecoder.decodeInlineElement] function that returns [Decoder]. -If your class should be represented as a primitive (as shown in [Primitive serializer](serializers.md#primitive-serializer) section), +If your class should be represented as a primitive (as shown in [Primitive serializer](create-custom-serializers.md#create-a-custom-primitive-serializer) section), and you cannot use [encodeStructure][Encoder.encodeStructure] function, there is a complementary function in [Encoder] called [encodeInline][Encoder.encodeInline]. We will use it to show an example how one can represent a class as an unsigned integer. From 77f9543cbf9bf9b29eb0bb43c91eec9b590dd374 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 17 Oct 2024 10:20:50 +0200 Subject: [PATCH 12/14] restructured document and migrated to writerside --- docs/cfg/buildprofiles.xml | 2 +- docs/serialization.tree | 41 ++- .../alternative-serialization-formats.md | 33 +-- docs/topics/configure-json-serialization.md | 9 +- docs/topics/create-custom-serializers.md | 43 ++- .../serialization-customization-options.md | 80 +++--- docs/topics/serialization-get-started.md | 12 +- .../serialization-json-configuration.md | 168 +++++++++--- docs/topics/serialization-json-elements.md | 77 +++++- docs/topics/serialization-polymorphism.md | 56 ++-- .../serialization-serialize-builtin-types.md | 15 +- docs/topics/serialization-transform-json.md | 31 ++- docs/topics/serialization.md | 2 +- docs/topics/third-party-classes.md | 16 +- docs/{project.ihp => writerside.cfg} | 9 +- guide/example/example-basic-01.kt | 12 - guide/example/example-basic-02.kt | 13 - guide/example/example-basic-03.kt | 15 -- guide/example/example-builtin-01.kt | 13 +- guide/example/example-builtin-02.kt | 13 +- guide/example/example-builtin-03.kt | 8 +- guide/example/example-builtin-04.kt | 5 +- guide/example/example-builtin-05.kt | 8 +- guide/example/example-builtin-06.kt | 7 +- guide/example/example-builtin-07.kt | 7 +- guide/example/example-builtin-08.kt | 7 +- guide/example/example-builtin-09.kt | 24 +- guide/example/example-builtin-10.kt | 27 +- guide/example/example-builtin-11.kt | 8 +- guide/example/example-builtin-12.kt | 5 +- guide/example/example-builtin-13.kt | 5 +- guide/example/example-classes-01.kt | 1 + guide/example/example-classes-02.kt | 3 +- guide/example/example-classes-03.kt | 1 + guide/example/example-classes-04.kt | 3 +- guide/example/example-classes-05.kt | 5 +- guide/example/example-classes-06.kt | 3 +- guide/example/example-classes-07.kt | 3 +- guide/example/example-classes-08.kt | 1 + guide/example/example-classes-09.kt | 1 + guide/example/example-classes-10.kt | 1 + guide/example/example-classes-11.kt | 3 +- guide/example/example-classes-12.kt | 5 +- guide/example/example-classes-13.kt | 5 +- guide/example/example-classes-14.kt | 1 + guide/example/example-classes-15.kt | 11 +- guide/example/example-formats-01.kt | 16 +- guide/example/example-formats-02.kt | 7 +- guide/example/example-formats-03.kt | 18 +- guide/example/example-formats-04.kt | 245 +++++++++++++++++- guide/example/example-formats-05.kt | 26 -- guide/example/example-formats-06.kt | 25 -- guide/example/example-formats-07.kt | 23 -- guide/example/example-formats-08.kt | 32 --- guide/example/example-formats-09.kt | 18 -- guide/example/example-formats-10.kt | 18 -- guide/example/example-formats-11.kt | 36 --- guide/example/example-formats-12.kt | 62 ----- guide/example/example-formats-13.kt | 64 ----- guide/example/example-formats-14.kt | 72 ----- guide/example/example-formats-15.kt | 78 ------ guide/example/example-formats-16.kt | 94 ------- guide/example/example-formats-17.kt | 135 ---------- guide/example/example-json-02.kt | 4 + guide/example/example-json-03.kt | 4 + guide/example/example-json-04.kt | 25 +- guide/example/example-json-05.kt | 30 ++- guide/example/example-json-06.kt | 31 +-- guide/example/example-json-07.kt | 23 +- guide/example/example-json-08.kt | 26 +- guide/example/example-json-09.kt | 24 +- guide/example/example-json-10.kt | 39 ++- guide/example/example-json-11.kt | 43 +-- guide/example/example-json-12.kt | 32 +-- guide/example/example-json-13.kt | 25 +- guide/example/example-json-14.kt | 9 +- guide/example/example-json-15.kt | 7 + guide/example/example-json-16.kt | 57 ---- guide/example/example-json-elements-01.kt | 9 + guide/example/example-json-elements-02.kt | 9 + guide/example/example-json-elements-03.kt | 10 + guide/example/example-json-elements-04.kt | 7 + guide/example/example-json-elements-05.kt | 7 + guide/example/example-json-elements-06.kt | 13 +- guide/example/example-json-elements-07.kt | 15 +- guide/example/example-json-elements-08.kt | 8 +- guide/example/example-json-elements-09.kt | 16 ++ guide/example/example-json-transform-01.kt | 15 +- guide/example/example-json-transform-02.kt | 17 +- guide/example/example-json-transform-03.kt | 9 +- guide/example/example-json-transform-04.kt | 8 +- guide/example/example-json-transform-05.kt | 14 +- guide/example/example-json-transform-06.kt | 15 +- guide/example/example-poly-01.kt | 9 +- guide/example/example-poly-02.kt | 14 +- guide/example/example-poly-03.kt | 11 +- guide/example/example-poly-04.kt | 13 +- guide/example/example-poly-05.kt | 13 +- guide/example/example-poly-06.kt | 10 +- guide/example/example-poly-07.kt | 13 +- guide/example/example-poly-08.kt | 13 +- guide/example/example-poly-09.kt | 15 +- guide/example/example-poly-10.kt | 11 +- guide/example/example-poly-11.kt | 8 +- guide/example/example-poly-12.kt | 16 +- guide/example/example-poly-13.kt | 26 +- guide/example/example-poly-14.kt | 34 ++- guide/example/example-poly-15.kt | 65 +++-- guide/example/example-poly-16.kt | 53 ++-- guide/example/example-poly-17.kt | 55 ++-- guide/example/example-poly-18.kt | 76 +++++- guide/example/example-poly-19.kt | 37 --- guide/example/example-poly-20.kt | 74 ------ guide/example/example-serializer-01.kt | 13 +- guide/example/example-serializer-02.kt | 48 +++- guide/example/example-serializer-03.kt | 43 ++- guide/example/example-serializer-04.kt | 44 +++- guide/example/example-serializer-05.kt | 64 ++++- guide/example/example-serializer-06.kt | 62 ++++- guide/example/example-serializer-07.kt | 40 +-- guide/example/example-serializer-08.kt | 44 ++-- guide/example/example-serializer-09.kt | 34 --- guide/example/example-serializer-10.kt | 36 --- guide/example/example-serializer-11.kt | 36 --- guide/example/example-serializer-12.kt | 52 ---- guide/example/example-serializer-13.kt | 56 ---- guide/example/example-serializer-19.kt | 26 -- guide/example/example-serializer-20.kt | 22 -- guide/example/example-serializer-21.kt | 35 --- guide/example/example-serializer-22.kt | 18 -- guide/example/example-serializer-23.kt | 28 -- ...ializer-14.kt => example-thirdparty-01.kt} | 11 +- ...ializer-15.kt => example-thirdparty-02.kt} | 12 +- ...ializer-16.kt => example-thirdparty-03.kt} | 12 +- ...ializer-17.kt => example-thirdparty-04.kt} | 16 +- ...ializer-18.kt => example-thirdparty-05.kt} | 20 +- guide/test/BuiltinClassesTest.kt | 10 +- guide/test/FormatsTest.kt | 69 ++--- guide/test/JsonTest.kt | 26 +- guide/test/JsonTestElements.kt | 7 + guide/test/PolymorphismTest.kt | 57 ++-- guide/test/SerializersTest.kt | 125 +-------- guide/test/SerializersThirdParty.kt | 56 ++++ 143 files changed, 1898 insertions(+), 2128 deletions(-) rename docs/{project.ihp => writerside.cfg} (53%) delete mode 100644 guide/example/example-basic-01.kt delete mode 100644 guide/example/example-basic-02.kt delete mode 100644 guide/example/example-basic-03.kt delete mode 100644 guide/example/example-formats-05.kt delete mode 100644 guide/example/example-formats-06.kt delete mode 100644 guide/example/example-formats-07.kt delete mode 100644 guide/example/example-formats-08.kt delete mode 100644 guide/example/example-formats-09.kt delete mode 100644 guide/example/example-formats-10.kt delete mode 100644 guide/example/example-formats-11.kt delete mode 100644 guide/example/example-formats-12.kt delete mode 100644 guide/example/example-formats-13.kt delete mode 100644 guide/example/example-formats-14.kt delete mode 100644 guide/example/example-formats-15.kt delete mode 100644 guide/example/example-formats-16.kt delete mode 100644 guide/example/example-formats-17.kt delete mode 100644 guide/example/example-json-16.kt create mode 100644 guide/example/example-json-elements-09.kt delete mode 100644 guide/example/example-poly-19.kt delete mode 100644 guide/example/example-poly-20.kt delete mode 100644 guide/example/example-serializer-09.kt delete mode 100644 guide/example/example-serializer-10.kt delete mode 100644 guide/example/example-serializer-11.kt delete mode 100644 guide/example/example-serializer-12.kt delete mode 100644 guide/example/example-serializer-13.kt delete mode 100644 guide/example/example-serializer-19.kt delete mode 100644 guide/example/example-serializer-20.kt delete mode 100644 guide/example/example-serializer-21.kt delete mode 100644 guide/example/example-serializer-22.kt delete mode 100644 guide/example/example-serializer-23.kt rename guide/example/{example-serializer-14.kt => example-thirdparty-01.kt} (70%) rename guide/example/{example-serializer-15.kt => example-thirdparty-02.kt} (74%) rename guide/example/{example-serializer-16.kt => example-thirdparty-03.kt} (74%) rename guide/example/{example-serializer-17.kt => example-thirdparty-04.kt} (65%) rename guide/example/{example-serializer-18.kt => example-thirdparty-05.kt} (74%) create mode 100644 guide/test/SerializersThirdParty.kt diff --git a/docs/cfg/buildprofiles.xml b/docs/cfg/buildprofiles.xml index f4123a1131..d2b2e970b6 100644 --- a/docs/cfg/buildprofiles.xml +++ b/docs/cfg/buildprofiles.xml @@ -6,5 +6,5 @@ https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/ true - + diff --git a/docs/serialization.tree b/docs/serialization.tree index 39c7217aca..785272791a 100644 --- a/docs/serialization.tree +++ b/docs/serialization.tree @@ -1,29 +1,24 @@ - - - - - - - + + + + + - - - + + + - - - - + + + + - + - - + + - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/docs/topics/alternative-serialization-formats.md b/docs/topics/alternative-serialization-formats.md index a32582b5dd..1babab2265 100644 --- a/docs/topics/alternative-serialization-formats.md +++ b/docs/topics/alternative-serialization-formats.md @@ -26,11 +26,13 @@ dependencies { } ``` + + -```text +```groovy dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-cbor:%serializationVersion%' } @@ -54,7 +56,6 @@ Let's look at an example where a `Project` object is serialized into a binary ar import kotlinx.serialization.* import kotlinx.serialization.cbor.* - fun ByteArray.toAsciiHexString() = joinToString("") { // // Converts bytes to ASCII characters if printable, otherwise shows their hexadecimal value if (it in 32..127) it.toInt().toChar().toString() else @@ -111,7 +112,7 @@ In [CBOR hex notation](http://cbor.me/), the output of the above example corresp > Unlike JSON, CBOR supports maps with non-trivial keys. However, some parsers, like `jackson-dataformat-cbor`, don't support this feature. > For JSON workaround, see the [Encode structured map keys](serialization-json-configuration.md#encode-structured-map-keys) section. > -{type="note"} +{style="note"} ### Ignore unknown keys in CBOR @@ -148,7 +149,7 @@ fun main() { > * 66: Length of the value "Kotlin" (6 characters). > * 4b6f746c696e: The value "Kotlin". > -{type="note"} +{style="note"} @@ -259,7 +260,7 @@ the encoding and verification of these tags. > For more information on tagging in CBOR, see [RFC 8949 Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items). > -{type="tip"} +{style="tip"} You can also tag entire classes using the [`@ObjectTags`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-object-tags/) annotation, which applies tags to all instances of the class. When serializing, `@ObjectTags` are always encoded directly before the data of the tagged object. @@ -301,12 +302,12 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:%serializationVersion%") } ``` - + -```text +```groovy dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:%serializationVersion%' } @@ -362,7 +363,7 @@ Project(name=kotlinx.serialization, language=Kotlin) In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following: -```text +```protobuf Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization" Field #2: 12 String Length = 6, Hex = 06, UTF8 = "Kotlin" ``` @@ -413,7 +414,7 @@ In the output, the `name` property uses field number 1 (`0A`), as specified. The > For more information about Protobuf field numbers, see the [Official Protobuf Language Guide](https://protobuf.dev/programming-guides/proto2/#assigning). > -{type="tip"} +{style="tip"} @@ -476,7 +477,7 @@ For example, it encodes the value of `3` as four bytes `03 00 00 00`. > `uintXX` and `sfixedXX` Protocol Buffer types are not supported. > -{type="note"} +{style="note"} @@ -649,7 +650,7 @@ The output shows how each oneof type is encoded: > Each `oneof` group must be tied to a single data class to prevent ID conflicts or runtime exceptions. > -{type="note"} +{style="note"} You can also define a class without the `@ProtoOneOf` annotation if you plan to use it only for deserialization. @@ -723,7 +724,7 @@ message SampleData { > Default values aren't represented in `.proto` files, so the schema includes a warning when they are present. > -{type="note"} +{style="note"} ## Properties (experimental) @@ -865,7 +866,7 @@ fun main() { ``` {kotlin-runnable="true"} - + Formats like JSON control how an object is encoded into bytes, but a serializer determines how the object’s properties are represented. -Automatically generated serializers with the `@Serializable` annotation can handle most common use cases, making it easy to serialize Kotlin objects without additional configuration. +The `@Serializable` annotation automatically generates a serializer, which can handle most common use cases, making it easy to serialize Kotlin objects without additional configuration. The Kotlin Serialization plugin automatically generates a `KSerializer` implementation for each class annotated with `@Serializable`, which you can retrieve using the `.serializer()` function: @@ -168,16 +168,16 @@ fun main() { {kotlin-runnable="true"} This array representation may not be ideal for JSON, but it becomes highly efficient when used with a `ByteArray` and binary formats, where it can significantly reduce data size. -For more information on how format can treat arrays, see the [Format specific types](formats.md#format-specific-types) section. +For more information on how non-JSON serialization formats treat arrays, see [Alternative and custom serialization formats](alternative-serialization-formats.md). > When using delegated serialization, you cannot rely on the default class serializer’s `descriptor`. > Formats that depend on schemas may incorrectly assume you are encoding a primitive type, like `Int`, > and expect `encodeInt()` to be called instead of the [`encodeSerializableValue()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html) function. > Similarly, using a specific serializer’s `descriptor` directly can lead to confusion in formats that treat certain types specially, like arrays. > -{type="note"} +{style="note"} - + + + + + + -Additionally, the type safety of Kotlin is strongly enforced. +Additionally, Kotlin's [null safety](null-safety.md) is strongly enforced. If a `null` value is encountered in a JSON object for a non-nullable Kotlin property, even if the property has a default value, an exception is raised: @@ -126,11 +125,11 @@ Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property -> If you need to handle `null` values from third-party JSON, you can [coerce them to a default value](json.md#coercing-input-values). +> If you need to handle `null` values from third-party JSON, you can [coerce them to a default value](serialization-json-configuration.md#coerce-input-values). > -{type="tip"} +{style="tip"} -When an optional property is present in the input, the initializer for that property is not called. +When an optional property is present in the input, the initializer for that property isn't called. In the following example, since the `language` property is specified in the input, the `Computing` string is not printed in the output: @@ -172,9 +171,9 @@ Project(name=kotlinx.serialization, language=Java) > This behavior is intended to improve performance. -> Avoid relying on any side effects in the initializer, as they will be bypassed if the initializer is not called. +> Avoid relying on initializer side effects, because they are bypassed if the initializer isn't called. > -{type="note"} +{style="note"} ### Serialization of class references @@ -216,16 +215,16 @@ fun main() { -> If you need to reference non-serializable classes, you can mark them as [transient properties](#exclude-properties-with-the-transient-annotation), or -> provide a [custom serializer](serializers.md) for them. +> To reference non-serializable classes, mark them as [transient properties](#exclude-properties-with-the-transient-annotation), or +> provide a [custom serializer](create-custom-serializers.md) for them. > -{type="tip"} +{style="tip"} ### Serialization of repeated object references -Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction +Kotlin Serialization is designed to encode and decode plain data. It doesn't support reconstruction of arbitrary object graphs with repeated object references. -For example, when serializing an object that references the same instance twice, it is simply encoded twice: +For example, when serializing an object that references the same instance twice, it's simply encoded twice: ```kotlin // Imports the necessary libraries @@ -261,13 +260,13 @@ fun main() { > If you attempt to serialize a circular structure, it will result in stack overflow. -> You can use the [Transient properties](#exclude-properties-with-the-transient-annotation) to exclude some references from serialization. +> To exclude references from serialization, use [the @Transient annotation](#exclude-properties-with-the-transient-annotation). > -{type="tip"} +{style="tip"} ### Generic class serialization -Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at +Generic classes in Kotlin support type-polymorphism, which is enforced by Kotlin Serialization at compile-time. For example, consider a generic serializable class `Box`: ```kotlin @@ -307,9 +306,8 @@ fun main() { -The type that is serialized to JSON depends on the actual compile-time type parameter specified for `Box`. - -If the generic type is not serializable, a compile-time error will occur, preventing the code from compiling. +The type serialized to JSON depends on the compile-time type parameter specified for `Box`. +If the generic type is not serializable, a compile-time error occurs. ## Customize serialization behavior @@ -321,7 +319,7 @@ This section covers techniques for customizing property names, managing default By default, the property names in the serialization output, such as JSON, match their names in the source code. These names, known as _serial names_, can be customized using the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation. -For example, you can customize a property’s serial name to be shorter or more descriptive in the serialized output: +You can use this annotation to customize a property’s serial name to be shorter or more descriptive in the serialized output: ```kotlin // Imports the necessary libraries @@ -357,9 +355,13 @@ fun main() { The `@Serializable` annotation requires all parameters of the class's primary constructor to be properties. -As a workaround, you can define a private primary constructor with the class's properties and create a -secondary constructor to handle the path string. -Serialization works with a private primary constructor and still serializes only the backing fields: +If your class initialization involves more complex logic, +such as processing a string into multiple parts, +you can define a secondary constructor to handle this logic. + +The primary constructor can remain private and be responsible for assigning the necessary properties. + +In the following example, the secondary constructor processes a string by splitting it into two parts, which are then passed to the primary constructor for serialization: ```kotlin // Imports the necessary libraries @@ -398,7 +400,7 @@ fun main() { ### Validate data in primary constructor -When you need to validate a constructor parameter before storing it in a property, +To validate a constructor parameter before storing it in a property, replace the parameter with a property in the primary constructor and perform validation in an `init` block. This ensures the class is serializable and that invalid data cannot be deserialized: @@ -440,9 +442,9 @@ Exception in thread "main" java.lang.IllegalArgumentException: name cannot be em ### Set default values for optional properties In Kotlin, an object can only be deserialized when all its properties are present in the input. -If a property is missing, deserialization fails. +If a property is missing, the deserialization fails. -To resolve this issue, you can add a default value to the property, which automatically makes it optional for +To resolve this issue, you can add a default value to properties, which automatically makes them optional for serialization: ```kotlin @@ -452,7 +454,7 @@ import kotlinx.serialization.json.* //sampleStart @Serializable -// Sets a default value for the optional `language` property +// Sets a default value for the optional language property data class Project(val name: String, val language: String = "Kotlin") fun main() { @@ -478,7 +480,7 @@ Project(name=kotlinx.serialization, language=Kotlin) ### Make properties required with the @Required annotation -A property with a default value can be made required in a serialized format with the [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) annotation. +You can make properties required in a serialized format with the [`@Required`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/) annotation. This ensures that the property must be present in the input, even if it has a default value: ```kotlin @@ -530,7 +532,7 @@ import kotlinx.serialization.json.Json //sampleStart @Serializable -// Excludes the `language` property from serialization +// Excludes the language property from serialization data class Project(val name: String, @Transient val language: String = "Kotlin") fun main() { @@ -556,9 +558,9 @@ Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys. > You can avoid exceptions from unknown keys in JSON, including those marked with the @Transient annotation, with the [`ignoreUnknownKeys`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html) setting. -> For more information, see the [Ignoring Unknown Keys](json.md#ignoring-unknown-keys) section. +> For more information, see the [Ignore unknown keys](serialization-json-configuration.md#ignore-unknown-keys) section. > -{type="tip"} +{style="tip"} ### Manage the serialization of default properties with @EncodedDefault @@ -594,14 +596,14 @@ fun main() { -> You can learn more about how this behavior can be configured in the JSON format in the [Encoding defaults](json.md#encoding-defaults) section. +> You can learn more about configuring this JSON behavior in the [Encode default values](serialization-json-configuration.md#encode-default-values) section. > -{type="tip"} +{style="tip"} To ensure that a property is always serialized, regardless of its value or format settings, use the [`@EncodeDefault`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/) annotation. It's also possible to do the opposite by configuring the [`EncodeDefault.Mode`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/) parameter. Let's look at an example, where the `language` property is included in the serialized output regardless of its value, -while the `projects` property is not serialized when it is an empty list: +while the `projects` property is not serialized when it's an empty list: ```kotlin // Imports the necessary libraries @@ -612,26 +614,26 @@ import kotlinx.serialization.json.* @Serializable data class Project( val name: String, - // The 'language' property will always be included in the serialized output, even if it has the default value "Kotlin" + // The language property is always included in the serialized output, even if it has the default value "Kotlin" @EncodeDefault val language: String = "Kotlin" ) @Serializable data class User( val name: String, - // The 'projects' property will never be included in the serialized output, even if it has a value - // Since the default value is an empty list, 'projects' will be omitted unless it contains elements + // The projects property is never included in the serialized output, even if it has a value + // Since the default value is an empty list, projects is omitted unless it contains elements @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() ) fun main() { val userA = User("Alice", listOf(Project("kotlinx.serialization"))) val userB = User("Bob") - // 'projects' is serialized because it contains a value, and 'language' is always serialized + // projects is serialized because it contains a value, and language is always serialized println(Json.encodeToString(userA)) // {"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} - // 'projects' is omitted because it's an empty list and EncodeDefault.Mode is set to NEVER, so it's not serialized + // projects is omitted because it's an empty list and EncodeDefault.Mode is set to NEVER, so it's not serialized println(Json.encodeToString(userB)) // {"name":"Bob"} } diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md index 38c3a50141..c6f6828d6b 100644 --- a/docs/topics/serialization-get-started.md +++ b/docs/topics/serialization-get-started.md @@ -142,7 +142,7 @@ In Kotlin, you can serialize objects to JSON using the `kotlinx.serialization` l To make a class serializable, you need to mark it with the [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) annotation. This annotation indicates to the compiler to generate the necessary code for serializing and deserializing instances of the class. -For more information, see [The @Serialization annotation](serialization.md#the-serializable-annotation) section. +For more information, see [The @Serialization annotation](serialization-customization-options.md#the-serializable-annotation) section. Let's look at an example: @@ -162,9 +162,9 @@ Let's look at an example: > The `@Serializable` annotation enables default serialization of all properties in the primary constructor. > You can customize serialization behavior using various techniques like custom constructors, optional properties, and more. - > For more information, see [Serialization customization options](serialization-customization-options.md). + > For more information, see [Serialize classes](serialization-customization-options.md). > - {type="note"} + {style="note"} 3. Serialize an instance of this class by calling the `Json.encodeToString()` function: @@ -187,7 +187,7 @@ Let's look at an example: ``` {kotlin-runnable="true"} - As a result, you get a string containing the state of this object in the JSON format: `{"a": 42, "b": "str"}` + As a result, you get a string containing the state of this object in JSON format: `{"a": 42, "b": "str"}` > You can also serialize a collection of objects in a single call: > @@ -196,7 +196,7 @@ Let's look at an example: > val jsonList = Json.encodeToString(dataList) > ``` > - {type="note"} + {style="note"} ## Deserialize objects from JSON @@ -238,6 +238,8 @@ To deserialize an object from JSON in Kotlin: ``` {kotlin-runnable="true"} +Congratulations! You have successfully serialized an object to JSON and deserialized it back into an object in Kotlin! + ## What's next? * Learn how to serialize standard types, including built-in types like numbers and strings, in [Serialize built-in types](serialization-serialize-builtin-types.md). diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md index 33a56de119..71dda0e257 100644 --- a/docs/topics/serialization-json-configuration.md +++ b/docs/topics/serialization-json-configuration.md @@ -21,7 +21,7 @@ println(jsonString) > Reusing custom `Json` instances improves performance by allowing them to cache class-specific information. > -{type="tip"} +{style="tip"} The following sections cover the various configuration features supported by `Json`. @@ -39,6 +39,7 @@ and line breaks for better readability, by setting the [`prettyPrint`](https://k import kotlinx.serialization.* import kotlinx.serialization.json.* +//sampleStart // Creates a custom Json format val format = Json { prettyPrint = true } @@ -51,7 +52,9 @@ fun main() { // Prints the pretty-printed JSON string println(format.encodeToString(data)) } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -84,6 +87,11 @@ To relax these restrictions, set the [`isLenient`](https://kotlinlang.org/api/ko This allows the parser to handle more freely formatted data: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart val format = Json { isLenient = true } enum class Status { SUPPORTED } @@ -104,9 +112,11 @@ fun main() { println(data) // Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000) } +//sampleEnd ``` +{kotlin-runnable="true"} - + @@ -160,11 +177,16 @@ and how to coerce unexpected or missing data into valid values during deserializ By default, the JSON serializer does not encode default property values because they are automatically assigned to missing fields during decoding. This behavior is especially useful for nullable properties with null defaults, as it avoids writing unnecessary `null` values. -For more details, see the [Manage serialization of default properties](serialization-customization-options.md#manage-serialization-of-default-properties-with-encodeddefault) section. +For more details, see the [Manage serialization of default properties](serialization-customization-options.md#manage-the-serialization-of-default-properties-with-encodeddefault) section. You can change this default behavior by setting the [`encodeDefaults`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html) property to `true`: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart // Configures a Json instance to encode default values val format = Json { encodeDefaults = true } @@ -182,9 +204,11 @@ fun main() { println(format.encodeToString(data)) // {"name":"kotlinx.serialization","language":"Kotlin","website":null} } +//sampleEnd ``` +{kotlin-runnable="true"} - + + @@ -251,8 +282,8 @@ Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null When working with JSON data from third parties, the format can evolve over time, leading to changes in field types. This can lead to exceptions during decoding when the actual values do not match the expected types. The default `Json` implementation is strict about input types, as demonstrated in -the [@Serializable annotation](serialization-customization-options.md#the-serializable-annotation) section. -You can relax this restriction using the [coerceInputValues](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html) property. +the [`@Serializable` annotation](serialization-customization-options.md#the-serializable-annotation) section. +You can relax this restriction using the [`coerceInputValues`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html) property. This property only affects decoding. It treats certain invalid input values as if the corresponding property were missing. The current supported invalid values are: @@ -263,13 +294,18 @@ The current supported invalid values are: > This list may be expanded in the future, making `Json` instances with this property even more permissive > by replacing invalid values with defaults or `null`. > -{type="note"} +{style="note"} If value is missing, it is replaced with a default property value if it exists. For enums, if no default is defined and the [`explicitNulls`]((#omit-explicit-nulls)) property is set to `false`, the value is replaced with `null` if the property is nullable: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart val format = Json { coerceInputValues = true } @Serializable @@ -284,9 +320,11 @@ fun main() { println(data) // Project(name=kotlinx.serialization, language=Kotlin) } +//sampleEnd ``` +{kotlin-runnable="true"} - + + + + + + + + @@ -603,6 +690,11 @@ However, third-party JSONs might have enum values in lowercase or mixed case. To handle such cases, you can configure the `Json` instance to decode enum values in a case-insensitive way using the [`JsonBuilder.decodeEnumsCaseInsensitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html) property: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart // Configures a Json instance to decode enum values in a case-insensitive way val format = Json { decodeEnumsCaseInsensitive = true } @@ -616,12 +708,14 @@ fun main() { println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) // CasesList(cases=[VALUE_A, VALUE_B]) } +//sampleEnd ``` +{kotlin-runnable="true"} > This property affects both serial names and alternative names specified with the `@JsonNames` annotation, > ensuring that all values are successfully decoded. This property does not affect encoding. > -{type="note"} +{style="note"} @@ -641,6 +735,11 @@ For these scenarios, you can specify a global naming strategy using the [JsonBui `kotlinx.serialization` provides a built-in strategy, [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html): ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable data class Project(val projectName: String, val projectOwner: String) @@ -653,7 +752,9 @@ fun main() { println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) // {"project_name":"kotlinx.serialization","project_owner":"Kotlin"} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -663,7 +764,7 @@ fun main() { ``` --> -When using a global naming strategy like [JsonNamingStrategy](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/), keep the following in mind: +When using a global naming strategy like [`JsonNamingStrategy`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/), keep the following in mind: * The naming strategy transformation applies to all properties, whether the serial name comes from the property name or is specified by the [`@SerialName`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/) annotation. This means you cannot avoid the transformation by explicitly specifying a serial name. @@ -677,3 +778,8 @@ This can make tasks like **Find Usages**, **Rename** in IDEs, or full-text searc Given these factors, it’s important to weigh the pros and cons before implementing global naming strategies in your application. + +## What's next? + +* Explore [advanced JSON element handling](serialization-json-elements.md) to manipulate and work with JSON data before it is parsed or serialized. +* Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. diff --git a/docs/topics/serialization-json-elements.md b/docs/topics/serialization-json-elements.md index b3732669e9..a5f341d71f 100644 --- a/docs/topics/serialization-json-elements.md +++ b/docs/topics/serialization-json-elements.md @@ -24,6 +24,11 @@ import kotlinx.serialization.json.* --> ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart fun main() { val element = Json.parseToJsonElement(""" {"name":"kotlinx.serialization","language":"Kotlin"} @@ -32,7 +37,9 @@ fun main() { println(element) // {"name":"kotlinx.serialization","language":"Kotlin"} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -69,6 +76,11 @@ Similar functions are available for other types, such as `long`, `longOrNull`, ` Here is an example of how you can use these functions when processing JSON data: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart fun main() { val element = Json.parseToJsonElement(""" { @@ -86,7 +98,9 @@ fun main() { println(sum) // 9042 } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -107,6 +121,11 @@ These provide a DSL for defining the JSON structure, similar to Kotlin’s stand Here is an example demonstrating the key features: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart fun main() { val element = buildJsonObject { // Adds a simple key-value pair to the JsonObject @@ -130,7 +149,9 @@ fun main() { println(element) // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -144,10 +165,15 @@ fun main() { ## Decode Json elements -You can decode an instance of the `JsonElement` class into a serializable object using +To decode an instance of the `JsonElement` class into a serializable object, use the [`Json.decodeFromJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) function: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @Serializable data class Project(val name: String, val language: String) @@ -162,7 +188,9 @@ fun main() { println(data) // Project(name=kotlinx.serialization, language=Kotlin) } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -178,7 +206,7 @@ Project(name=kotlinx.serialization, language=Kotlin) > The [`JsonUnquotedLiteral`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html) functionality is [Experimental](components-stability.md#stability-levels-explained). To opt in, use the `@ExperimentalSerializationApi` annotation or the compiler option -opt-in=kotlinx.serialization.ExperimentalSerializationApi. > -{type="warning"} +{style="warning"} You can encode an arbitrary unquoted value with [`JsonUnquotedLiteral`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html). @@ -190,8 +218,12 @@ Kotlin/JVM [`BigDecimal`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/ja large numbers without loss of precision, but using `JsonPrimitive()` encodes the value as a string rather than as a number: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* import java.math.BigDecimal +//sampleStart val format = Json { prettyPrint = true } fun main() { @@ -211,11 +243,13 @@ fun main() { // "pi_double": 3.141592653589793, // "pi_string": "3.141592653589793238462643383279" } +//sampleEnd ``` +{kotlin-runnable="true"} -In the example above, even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. +In the example above, even though `pi` is defined as a number with 30 decimal places, the resulting JSON doesn't reflect this. The `Double` value is truncated to 15 decimal places, and the `String` is wrapped in quotes, making it a string instead of a JSON number. @@ -278,8 +318,12 @@ fun main() { To decode `pi` back to a `BigDecimal`, you can extract the string content of the `JsonPrimitive`: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* import java.math.BigDecimal +//sampleStart fun main() { val piObjectJson = """ { @@ -299,12 +343,14 @@ fun main() { println(pi) // 3.141592653589793238462643383279 } +//sampleEnd ``` +{kotlin-runnable="true"} -> This example uses a `JsonPrimitive` for simplicity. For a more reusable method of handling serialization, see -> [Json Transformations](serialization-transform-json.md).) +> This example uses a `JsonPrimitive` for simplicity. For more reusable methods of handling serialization, see +> [Json Transformations](serialization-transform-json.md). > -{type="note"} +{style="note"} @@ -319,13 +365,20 @@ fun main() { Finally, to avoid creating an inconsistent state, encoding a string equal to `"null"` with `JsonUnquotedLiteral` results in an exception. ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart @OptIn(ExperimentalSerializationApi::class) fun main() { // Caution: creating null with JsonUnquotedLiteral causes an exception! JsonUnquotedLiteral("null") // Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -340,13 +393,20 @@ Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingExcep You can use [`JsonNull`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-null/) or `JsonPrimitive` to represent a proper JSON `null` value instead: ```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart fun main() { val possiblyNull = JsonNull println(possiblyNull) // null } +//sampleEnd ``` +{kotlin-runnable="true"} @@ -357,3 +417,8 @@ null --> + +## What's next? + +* Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. +* Learn how to [serialize classes](serialization-customization-options.md) and how to modify the default behavior of the `@Serializable` annotation. diff --git a/docs/topics/serialization-polymorphism.md b/docs/topics/serialization-polymorphism.md index 70a8d5089d..c701d7b14e 100644 --- a/docs/topics/serialization-polymorphism.md +++ b/docs/topics/serialization-polymorphism.md @@ -78,7 +78,7 @@ fun main() { ``` {kotlin-runnable="true"} - + + + + + + + > The properties of objects are not serialized. > -{type="note"} +{style="note"} @@ -433,7 +433,7 @@ but allows the serialization of open polymorphic classes. > For JS and Native platforms, use an explicit serializer: `format.encodeToString(PolymorphicSerializer(Project::class), data)` > You can keep track of this issue [here](https://github.com/Kotlin/kotlinx.serialization/issues/1077). > -{type="note"} +{style="note"} ### Serialize interfaces @@ -487,7 +487,7 @@ fun main() { > For Kotlin/Native, you need to explicitly specify the serializer using > `format.encodeToString(PolymorphicSerializer(Project::class), data))` due to the platform's limited reflection capabilities. > -{type="note"} +{style="note"} @@ -586,7 +586,7 @@ fun main() { ``` {kotlin-runnable="true"} - + + ### Serialize non-serializable properties polymorphically When a property has a non-serializable type, such as `Any`, you can explicitly mark it with @@ -648,7 +650,7 @@ fun main() { ``` {kotlin-runnable="true"} - + + + + + + > You can also specify a serializer like `LongAsStringSerializer` for all properties in a file. -> For more information, see the [Specify serializers for a file](third-party-classes.md#specify-serializers-for-a-file) section for more details. +> For more information, see the [Specify serializers for a file](third-party-classes.md#specify-serializers-for-a-file) section. > -{type="tip"} +{style="tip"} @@ -166,7 +166,7 @@ fun main() { > On Kotlin/JS and Kotlin/Native, you must use the `@Serializable` annotation for an enum class if you want to use as a root object, > such as in `encodeToString(Status.SUPPORTED)`. > -{type="note"} +{style="note"} @@ -174,7 +174,7 @@ fun main() { > For more information on customizing serial names, see the [Customize serial names](serialization-customization-options.md#customize-serial-names) section. > -{type="tip"} +{style="tip"} To customize the serial names of enum entries, apply the `@SerialName` annotation to the entries and annotate the entire enum class with `@Serializable`: @@ -359,7 +359,7 @@ In JSON, Kotlin maps are represented as objects. Since JSON object keys are alwa > JSON doesn't natively support complex or composite keys. > To work around this and use structured objects as map keys, see the [Encode structured map keys](serialization-json-configuration.md#encode-structured-map-keys) section. > -{type="note"} +{style="note"} + +## What's next + +* Dive into the [Serialize classes](serialization-customization-options.md) section to learn how to serialize classes and how to modify the default behavior of the `@Serializable` annotation. +* To explore more complex JSON serialization scenarios, see [JSON serialization overview](configure-json-serialization.md). diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md index 315bdce607..d29508a61b 100644 --- a/docs/topics/serialization-transform-json.md +++ b/docs/topics/serialization-transform-json.md @@ -1,16 +1,16 @@ [//]: # (title: Transform JSON output) +> This guide builds upon concepts introduced in the [Serialize polymorphic classes](serialization-polymorphism.md) and [Create custom serializers](create-custom-serializers.md) guides. +> +{style="note"} + To modify the structure and content of JSON after serialization, or adapt input for deserialization, you can create a [custom serializer](create-custom-serializers.md). While the [`Encoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/) and [`Decoder`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/) offer precise control, Kotlin serialization also provides an API that makes it easy to manipulate a JSON elements tree. This can be ideal for smaller tasks or quick transformations. -> For an extensive guide about how to create custom serializers and how custom serializers are bound to classes, see [Create custom serializers](create-custom-serializers.md). -> -{type="note"} - The transformation features are available through the abstract [`JsonTransformingSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/) class, which implements [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/). Instead of interacting directly with `Encoder` or `Decoder`, this class allows you to define transformations using the `transformSerialize()` and `transformDeserialize()` functions for the JSON tree @@ -80,7 +80,7 @@ fun main() { Since this example focuses on deserialization, the `UserListSerializer` only overrides the `transformDeserialize()` function. The `JsonTransformingSerializer` constructor takes the original serializer -as parameter. For more information, see the [Constructing collection serializers](serializers.md#constructing-collection-serializers) section. +as parameter. @@ -151,11 +151,10 @@ You can omit properties from the JSON output when they have default values, matc This helps streamline the data, reducing unnecessary information while ensuring that only relevant properties are serialized. Let's look at an example where the `Project` class has a `language` property that should be omitted from the JSON output when its value is `"Kotlin"`. -To do this, you can write a custom `ProjectSerializer` based on the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) for the `Project` class: +To do this, you can write a custom serializer for the `Project` class: In the example below, we are serializing the `Project` class at the top-level, so we explicitly -pass the above `ProjectSerializer` to [Json.encodeToString] function as was shown in -the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: +pass the above `ProjectSerializer` to `encodeToString()` function: ```kotlin // Imports the necessary libraries @@ -189,10 +188,10 @@ fun main() { {kotlin-runnable="true} -> When serializing an object directly, you need to explicitly pass the custom serializer to the `Json.encodeToString()` -> function to ensure that the custom serialization logic is applied. For more information, see the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section. +> When serializing an object directly, you need to explicitly pass the custom serializer to the `encodeToString()` +> function to ensure that the custom serialization logic is applied. For more information, see the [Pass serializers manually](third-party-classes.html#pass-serializers-manually) section. > -{type="note"} +{style="note"} @@ -216,7 +215,7 @@ This serializer allows you to override the `selectDeserializer()` function to ch > When you use this serializer, the appropriate deserializer is chosen at runtime. > It can either come from the [registered](serialization-polymorphism.md#serialize-closed-polymorphic-classes) or the default serializer. > -{type="tip"} +{style="tip"} ```kotlin // Imports the necessary libraries @@ -263,7 +262,7 @@ fun main() { {kotlin-runnable="true"} This example manually selects the appropriate subclass without using plugin-generated code, -which is why the class does not need to be sealed, as recommended in the [Serialize closed polymorphic classes](serialization-polymorphism.md#serialize-closed-polymorphic-classes) section. +which is why the class doesn't need to be `sealed`. @@ -303,7 +302,6 @@ import kotlinx.serialization.json.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -//sampleStart // Defines a sealed class for API responses @Serializable(with = ResponseSerializer::class) sealed class Response { @@ -445,3 +443,8 @@ UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","l --> + +## What's next + +* Learn how to [serialize polymorphic classes](serialization-polymorphism.md) and handle objects of various types within a shared hierarchy. +* Discover [alternative experimental serialization formats](alternative-serialization-formats.md), such as CBOR and ProtoBuf, to improve performance and flexibility for your applications. diff --git a/docs/topics/serialization.md b/docs/topics/serialization.md index 1d4f80c045..2bfe4c1c4a 100644 --- a/docs/topics/serialization.md +++ b/docs/topics/serialization.md @@ -66,7 +66,7 @@ For more information, see [Serialize built-in types](serialization-serialize-bui > Not all types from the Kotlin standard library are serializable. In particular, [ranges](ranges.md) and the [`Regex`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/) class are not serializable at the moment. > Support for their serialization may be added in the future. > -{type="note"} +{style="note"} Additionally, classes annotated with `@Serializable` are fully supported for serialization, enabling the conversion of class instances to and from formats like JSON. For more information, see [Serialize classes](serialization-customization-options.md). diff --git a/docs/topics/third-party-classes.md b/docs/topics/third-party-classes.md index dc15a06803..d2b296238b 100644 --- a/docs/topics/third-party-classes.md +++ b/docs/topics/third-party-classes.md @@ -2,7 +2,7 @@ -Third-party types, such as [java.util.Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html), cannot be directly annotated with [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) because you do not have control over their source code. +Third-party types, such as [java.util.Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html), cannot be directly annotated with [`@Serializable`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/) because their source code cannot be modified. To serialize these non-serializable classes, you can implement a custom [`KSerializer`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/), as described in the [Create a custom primitive serializer](create-custom-serializers.md#create-a-custom-primitive-serializer) section. In this section, you can explore several approaches to working around this limitation, using `java.util.Date` as an example. @@ -40,7 +40,7 @@ fun main() { ``` {kotlin-runnable="true"} - + + + + + - \ No newline at end of file + diff --git a/docs/project.ihp b/docs/writerside.cfg similarity index 53% rename from docs/project.ihp rename to docs/writerside.cfg index 5f128b0a42..a011270f4d 100644 --- a/docs/project.ihp +++ b/docs/writerside.cfg @@ -1,14 +1,15 @@ - - + + - - + + + diff --git a/guide/example/example-basic-01.kt b/guide/example/example-basic-01.kt deleted file mode 100644 index 7d0d17a952..0000000000 --- a/guide/example/example-basic-01.kt +++ /dev/null @@ -1,12 +0,0 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. -package example.exampleBasic01 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) -} diff --git a/guide/example/example-basic-02.kt b/guide/example/example-basic-02.kt deleted file mode 100644 index 3a5ebb9408..0000000000 --- a/guide/example/example-basic-02.kt +++ /dev/null @@ -1,13 +0,0 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. -package example.exampleBasic02 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -@Serializable -class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) -} diff --git a/guide/example/example-basic-03.kt b/guide/example/example-basic-03.kt deleted file mode 100644 index f70369e5d4..0000000000 --- a/guide/example/example-basic-03.kt +++ /dev/null @@ -1,15 +0,0 @@ -// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit. -package example.exampleBasic03 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Json.decodeFromString(""" - {"name":"kotlinx.serialization","language":"Kotlin"} - """) - println(data) -} diff --git a/guide/example/example-builtin-01.kt b/guide/example/example-builtin-01.kt index ee2044497f..f62d427656 100644 --- a/guide/example/example-builtin-01.kt +++ b/guide/example/example-builtin-01.kt @@ -1,18 +1,17 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin01 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* - import kotlin.math.* @Serializable -class Data( - val answer: Int, - val pi: Double -) +class Data(val signature: Long) fun main() { - val data = Data(42, PI) + val data = Data(0x1CAFE2FEED0BABE0) println(Json.encodeToString(data)) + // {"signature":2067120338512882656} } diff --git a/guide/example/example-builtin-02.kt b/guide/example/example-builtin-02.kt index 351039cc21..40ff7c5a7e 100644 --- a/guide/example/example-builtin-02.kt +++ b/guide/example/example-builtin-02.kt @@ -1,13 +1,20 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin02 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* +import kotlin.math.* @Serializable -class Data(val signature: Long) +class Data( + val answer: Int, + val pi: Double +) fun main() { - val data = Data(0x1CAFE2FEED0BABE0) + val data = Data(42, PI) println(Json.encodeToString(data)) + // {"answer":42,"pi":3.141592653589793} } diff --git a/guide/example/example-builtin-03.kt b/guide/example/example-builtin-03.kt index 211ca06737..14234b8c37 100644 --- a/guide/example/example-builtin-03.kt +++ b/guide/example/example-builtin-03.kt @@ -1,10 +1,11 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin03 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* - import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import kotlin.math.* @Serializable class Data( @@ -15,4 +16,5 @@ class Data( fun main() { val data = Data(0x1CAFE2FEED0BABE0) println(Json.encodeToString(data)) + // {"signature":"2067120338512882656"} } diff --git a/guide/example/example-builtin-04.kt b/guide/example/example-builtin-04.kt index 3ffa21fe39..515fea7175 100644 --- a/guide/example/example-builtin-04.kt +++ b/guide/example/example-builtin-04.kt @@ -1,7 +1,9 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin04 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* // The @Serializable annotation is not needed for enum classes @@ -13,4 +15,5 @@ class Project(val name: String, val status: Status) fun main() { val data = Project("kotlinx.serialization", Status.SUPPORTED) println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","status":"SUPPORTED"} } diff --git a/guide/example/example-builtin-05.kt b/guide/example/example-builtin-05.kt index 148a335a6c..f96be05945 100644 --- a/guide/example/example-builtin-05.kt +++ b/guide/example/example-builtin-05.kt @@ -1,10 +1,13 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin05 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* -@Serializable // required because of @SerialName +// Requires the @Serializable annotation because of @SerialName +@Serializable enum class Status { @SerialName("maintained") SUPPORTED } @Serializable @@ -13,4 +16,5 @@ class Project(val name: String, val status: Status) fun main() { val data = Project("kotlinx.serialization", Status.SUPPORTED) println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","status":"maintained"} } diff --git a/guide/example/example-builtin-06.kt b/guide/example/example-builtin-06.kt index 00acfafb1a..bbed560976 100644 --- a/guide/example/example-builtin-06.kt +++ b/guide/example/example-builtin-06.kt @@ -1,7 +1,9 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin06 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable @@ -10,4 +12,5 @@ class Project(val name: String) fun main() { val pair = 1 to Project("kotlinx.serialization") println(Json.encodeToString(pair)) -} + // {"first":1,"second":{"name":"kotlinx.serialization"}} +} diff --git a/guide/example/example-builtin-07.kt b/guide/example/example-builtin-07.kt index e88b83f1a2..9e7a95d519 100644 --- a/guide/example/example-builtin-07.kt +++ b/guide/example/example-builtin-07.kt @@ -1,7 +1,9 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin07 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable @@ -13,4 +15,5 @@ fun main() { Project("kotlinx.coroutines") ) println(Json.encodeToString(list)) -} + // [{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}] +} diff --git a/guide/example/example-builtin-08.kt b/guide/example/example-builtin-08.kt index 93f1858154..ff29ca68ee 100644 --- a/guide/example/example-builtin-08.kt +++ b/guide/example/example-builtin-08.kt @@ -1,7 +1,9 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin08 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable @@ -13,4 +15,5 @@ fun main() { Project("kotlinx.coroutines") ) println(Json.encodeToString(set)) -} + // [{"name":"kotlinx.serialization"},{"name":"kotlinx.coroutines"}] +} diff --git a/guide/example/example-builtin-09.kt b/guide/example/example-builtin-09.kt index 90215282bc..bdbcdecf39 100644 --- a/guide/example/example-builtin-09.kt +++ b/guide/example/example-builtin-09.kt @@ -1,21 +1,19 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin09 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable -data class Data( - val a: List, - val b: Set -) - +class Project(val name: String) + fun main() { - val data = Json.decodeFromString(""" - { - "a": [42, 42], - "b": [42, 42] - } - """) - println(data) + val map = mapOf( + 1 to Project("kotlinx.serialization"), + 2 to Project("kotlinx.coroutines") + ) + println(Json.encodeToString(map)) + // {"1":{"name":"kotlinx.serialization"},"2":{"name":"kotlinx.coroutines"}} } diff --git a/guide/example/example-builtin-10.kt b/guide/example/example-builtin-10.kt index 07e6fca02c..eba299b946 100644 --- a/guide/example/example-builtin-10.kt +++ b/guide/example/example-builtin-10.kt @@ -1,16 +1,25 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin10 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable -class Project(val name: String) - +data class Data( + val a: List, + val b: Set +) + fun main() { - val map = mapOf( - 1 to Project("kotlinx.serialization"), - 2 to Project("kotlinx.coroutines") - ) - println(Json.encodeToString(map)) -} + val data = Json.decodeFromString(""" + { + "a": [42, 42], + "b": [42, 42] + } + """) + // No duplicate values in data.b property, because it is a Set + println(data) + // Data(a=[42, 42], b=[42]) +} diff --git a/guide/example/example-builtin-11.kt b/guide/example/example-builtin-11.kt index 88950d8741..c1b787ed36 100644 --- a/guide/example/example-builtin-11.kt +++ b/guide/example/example-builtin-11.kt @@ -1,7 +1,9 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin11 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable @@ -11,5 +13,7 @@ object SerializationVersion { fun main() { println(Json.encodeToString(SerializationVersion)) + // {} println(Json.encodeToString(Unit)) -} + // {} +} diff --git a/guide/example/example-builtin-12.kt b/guide/example/example-builtin-12.kt index 4bd1da05e8..48fa2511b8 100644 --- a/guide/example/example-builtin-12.kt +++ b/guide/example/example-builtin-12.kt @@ -1,12 +1,13 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin12 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlin.time.* fun main() { val duration = 1000.toDuration(DurationUnit.SECONDS) println(Json.encodeToString(duration)) + // "PT16M40S" } diff --git a/guide/example/example-builtin-13.kt b/guide/example/example-builtin-13.kt index d1a341867a..5289bf69ff 100644 --- a/guide/example/example-builtin-13.kt +++ b/guide/example/example-builtin-13.kt @@ -1,7 +1,9 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.exampleBuiltin13 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @Serializable @@ -12,4 +14,5 @@ sealed class ParametrizedParent { fun main() { println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42))) + // {"value":42} } diff --git a/guide/example/example-classes-01.kt b/guide/example/example-classes-01.kt index 3ba2f43a17..dedfcda5f3 100644 --- a/guide/example/example-classes-01.kt +++ b/guide/example/example-classes-01.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses01 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-classes-02.kt b/guide/example/example-classes-02.kt index 1ba55574b8..172d1f4239 100644 --- a/guide/example/example-classes-02.kt +++ b/guide/example/example-classes-02.kt @@ -1,11 +1,12 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses02 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -// The 'renamedTo' property is nullable and defaults to null, and it's not encoded +// The renamedTo property is nullable and defaults to null, and it's not encoded class Project(val name: String, val renamedTo: String? = null) fun main() { diff --git a/guide/example/example-classes-03.kt b/guide/example/example-classes-03.kt index f3592ab3cb..bc8f297311 100644 --- a/guide/example/example-classes-03.kt +++ b/guide/example/example-classes-03.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses03 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-classes-04.kt b/guide/example/example-classes-04.kt index 92c8b179c9..3574caef81 100644 --- a/guide/example/example-classes-04.kt +++ b/guide/example/example-classes-04.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses04 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @@ -10,7 +11,7 @@ fun computeLanguage(): String { } @Serializable -// Initializer is skipped if `language` is in input +// Initializer is skipped if language is in input data class Project(val name: String, val language: String = computeLanguage()) fun main() { diff --git a/guide/example/example-classes-05.kt b/guide/example/example-classes-05.kt index e95aca4bce..820d6e668e 100644 --- a/guide/example/example-classes-05.kt +++ b/guide/example/example-classes-05.kt @@ -1,14 +1,15 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses05 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -// The 'owner' property references another serializable class `User` +// The owner property references another serializable class `User` class Project(val name: String, val owner: User) -// The referenced class must also be annotated with `@Serializable` +// The referenced class must also be annotated with @Serializable @Serializable class User(val name: String) diff --git a/guide/example/example-classes-06.kt b/guide/example/example-classes-06.kt index ca1cd2faf9..f3f2bce4d5 100644 --- a/guide/example/example-classes-06.kt +++ b/guide/example/example-classes-06.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses06 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @@ -12,7 +13,7 @@ class User(val name: String) fun main() { val owner = User("kotlin") - // 'owner' is referenced twice + // owner is referenced twice val data = Project("kotlinx.serialization", owner, owner) println(Json.encodeToString(data)) // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}} diff --git a/guide/example/example-classes-07.kt b/guide/example/example-classes-07.kt index d9022e785a..80bb338725 100644 --- a/guide/example/example-classes-07.kt +++ b/guide/example/example-classes-07.kt @@ -1,11 +1,12 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses07 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -// The `Box` class can be used with built-in types like `Int`, or with user-defined types like `Project`. +// The Box class can be used with built-in types like Int, or with user-defined types like Project. class Box(val contents: T) @Serializable data class Project(val name: String, val language: String) diff --git a/guide/example/example-classes-08.kt b/guide/example/example-classes-08.kt index 745cfd15f1..1a11b8eb07 100644 --- a/guide/example/example-classes-08.kt +++ b/guide/example/example-classes-08.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses08 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-classes-09.kt b/guide/example/example-classes-09.kt index 9061e68a8c..fe4c787ae1 100644 --- a/guide/example/example-classes-09.kt +++ b/guide/example/example-classes-09.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses09 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-classes-10.kt b/guide/example/example-classes-10.kt index f9be1730ef..ef4a298d2b 100644 --- a/guide/example/example-classes-10.kt +++ b/guide/example/example-classes-10.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses10 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-classes-11.kt b/guide/example/example-classes-11.kt index 1b630bf14f..42526b5bbf 100644 --- a/guide/example/example-classes-11.kt +++ b/guide/example/example-classes-11.kt @@ -1,11 +1,12 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses11 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -// Sets a default value for the optional `language` property +// Sets a default value for the optional language property data class Project(val name: String, val language: String = "Kotlin") fun main() { diff --git a/guide/example/example-classes-12.kt b/guide/example/example-classes-12.kt index dfc85ea127..0bde58335e 100644 --- a/guide/example/example-classes-12.kt +++ b/guide/example/example-classes-12.kt @@ -1,13 +1,10 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses12 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - -// Imports the necessary libraries import kotlinx.serialization.Required -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json @Serializable // Marks the `language` property as required diff --git a/guide/example/example-classes-13.kt b/guide/example/example-classes-13.kt index 4a95a09bca..218d0fb082 100644 --- a/guide/example/example-classes-13.kt +++ b/guide/example/example-classes-13.kt @@ -1,16 +1,13 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses13 -import kotlinx.serialization.* -import kotlinx.serialization.json.* - // Imports the necessary libraries import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.json.Json @Serializable -// Excludes the `language` property from serialization +// Excludes the language property from serialization data class Project(val name: String, @Transient val language: String = "Kotlin") fun main() { diff --git a/guide/example/example-classes-14.kt b/guide/example/example-classes-14.kt index c56657c843..dee680d8bc 100644 --- a/guide/example/example-classes-14.kt +++ b/guide/example/example-classes-14.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses14 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* diff --git a/guide/example/example-classes-15.kt b/guide/example/example-classes-15.kt index 62308e8a36..3ef72ee044 100644 --- a/guide/example/example-classes-15.kt +++ b/guide/example/example-classes-15.kt @@ -1,32 +1,33 @@ // This file was automatically generated from serialization-customization-options.md by Knit tool. Do not edit. package example.exampleClasses15 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable data class Project( val name: String, - // The 'language' property will always be included in the serialized output, even if it has the default value "Kotlin" + // The language property is always included in the serialized output, even if it has the default value "Kotlin" @EncodeDefault val language: String = "Kotlin" ) @Serializable data class User( val name: String, - // The 'projects' property will never be included in the serialized output, even if it has a value - // Since the default value is an empty list, 'projects' will be omitted unless it contains elements + // The projects property is never included in the serialized output, even if it has a value + // Since the default value is an empty list, projects is omitted unless it contains elements @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List = emptyList() ) fun main() { val userA = User("Alice", listOf(Project("kotlinx.serialization"))) val userB = User("Bob") - // 'projects' is serialized because it contains a value, and 'language' is always serialized + // projects is serialized because it contains a value, and language is always serialized println(Json.encodeToString(userA)) // {"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]} - // 'projects' is omitted because it's an empty list and EncodeDefault.Mode is set to NEVER, so it's not serialized + // projects is omitted because it's an empty list and EncodeDefault.Mode is set to NEVER, so it's not serialized println(Json.encodeToString(userB)) // {"name":"Bob"} } diff --git a/guide/example/example-formats-01.kt b/guide/example/example-formats-01.kt index ad2b3e6d74..34bd10b84e 100644 --- a/guide/example/example-formats-01.kt +++ b/guide/example/example-formats-01.kt @@ -1,10 +1,12 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. +// This file was automatically generated from alternative-serialization-formats.md by Knit tool. Do not edit. package example.exampleFormats01 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.cbor.* fun ByteArray.toAsciiHexString() = joinToString("") { + // // Converts bytes to ASCII characters if printable, otherwise shows their hexadecimal value if (it in 32..127) it.toInt().toChar().toString() else "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" } @@ -13,9 +15,17 @@ fun ByteArray.toAsciiHexString() = joinToString("") { data class Project(val name: String, val language: String) fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val bytes = Cbor.encodeToByteArray(data) + val data = Project("kotlinx.serialization", "Kotlin") + + // Serializes the object into a CBOR binary array + val bytes = Cbor.encodeToByteArray(data) + + // Converts the binary array to a human-readable hex string println(bytes.toAsciiHexString()) + // {BF}dnameukotlinx.serializationhlanguagefKotlin{FF} + + // Deserializes the binary array back into a Project object val obj = Cbor.decodeFromByteArray(bytes) println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) } diff --git a/guide/example/example-formats-02.kt b/guide/example/example-formats-02.kt index c95e532946..b3ece3551a 100644 --- a/guide/example/example-formats-02.kt +++ b/guide/example/example-formats-02.kt @@ -1,9 +1,11 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. +// This file was automatically generated from alternative-serialization-formats.md by Knit tool. Do not edit. package example.exampleFormats02 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.cbor.* +// Sets ignoreUnknownKeys to true to allow unknown keys during deserialization val format = Cbor { ignoreUnknownKeys = true } @Serializable @@ -11,7 +13,10 @@ data class Project(val name: String) fun main() { val data = format.decodeFromHexString( + // CBOR hex notation input with an extra, unknown "language" key "bf646e616d65756b6f746c696e782e73657269616c697a6174696f6e686c616e6775616765664b6f746c696eff" ) + // Prints the deserialized Project object, ignoring the language property println(data) + // Project(name=kotlinx.serialization) } diff --git a/guide/example/example-formats-03.kt b/guide/example/example-formats-03.kt index d2191f6a0f..8197299523 100644 --- a/guide/example/example-formats-03.kt +++ b/guide/example/example-formats-03.kt @@ -1,6 +1,7 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. +// This file was automatically generated from alternative-serialization-formats.md by Knit tool. Do not edit. package example.exampleFormats03 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.cbor.* @@ -11,15 +12,22 @@ fun ByteArray.toAsciiHexString() = joinToString("") { @Serializable data class Data( + // Encodes the byte array as CBOR major type 2 as a continuous byte string @ByteString - val type2: ByteArray, // CBOR Major type 2 - val type4: ByteArray // CBOR Major type 4 + val type2: ByteArray, + // Encodes the byte array as CBOR major type 4 as an array of individual data items + val type4: ByteArray ) fun main() { - val data = Data(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8)) - val bytes = Cbor.encodeToByteArray(data) + // Creates a Data object with two ByteArray fields + val data = Data(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8)) + // Serializes the Data object into a CBOR byte array + val bytes = Cbor.encodeToByteArray(data) println(bytes.toAsciiHexString()) + // {BF}etype2D{01}{02}{03}{04}etype4{9F}{05}{06}{07}{08}{FF}{FF} + val obj = Cbor.decodeFromByteArray(bytes) println(obj) + // Data(type2=[1, 2, 3, 4], type4=[5, 6, 7, 8]) } diff --git a/guide/example/example-formats-04.kt b/guide/example/example-formats-04.kt index 9f6610bc10..6258bd56d4 100644 --- a/guide/example/example-formats-04.kt +++ b/guide/example/example-formats-04.kt @@ -1,6 +1,7 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. +// This file was automatically generated from alternative-serialization-formats.md by Knit tool. Do not edit. package example.exampleFormats04 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.protobuf.* @@ -12,10 +13,252 @@ fun ByteArray.toAsciiHexString() = joinToString("") { @Serializable data class Project(val name: String, val language: String) +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", "Kotlin") + // Serializes the Project instance into a ProtoBuf byte array + val bytes = ProtoBuf.encodeToByteArray(data) + + // Converts the byte array into a readable hex string for demonstration + println(bytes.toAsciiHexString()) + // {0A}{15}kotlinx.serialization{12}{06}Kotlin + + val obj = ProtoBuf.decodeFromByteArray(bytes) + println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +data class Project( + // Assigns field number 1 to the name property + @ProtoNumber(1) + val name: String, + // Assigns field number 3 to the language property + @ProtoNumber(3) + val language: String +) + +@OptIn(ExperimentalSerializationApi::class) fun main() { val data = Project("kotlinx.serialization", "Kotlin") val bytes = ProtoBuf.encodeToByteArray(data) println(bytes.toAsciiHexString()) + // {0A}{15}kotlinx.serialization{1A}{06}Kotlin val obj = ProtoBuf.decodeFromByteArray(bytes) println(obj) + // Project(name=kotlinx.serialization, language=Kotlin) +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +class Data( + // Uses DEFAULT encoding, optimized for small non-negative numbers + @ProtoType(ProtoIntegerType.DEFAULT) + val a: Int, + // Uses SIGNED encoding, optimized for small signed integers + @ProtoType(ProtoIntegerType.SIGNED) + val b: Int, + // Uses FIXED encoding, which always uses a fixed number of bytes + @ProtoType(ProtoIntegerType.FIXED) + val c: Int +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Data(1, -2, 3) + println(ProtoBuf.encodeToByteArray(data).toAsciiHexString()) + // {08}{01}{10}{03}{1D}{03}{00}{00}{00} +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +fun ByteArray.toAsciiHexString() = joinToString("") { + if (it in 32..127) it.toInt().toChar().toString() else + "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" +} + +@Serializable +data class Data( + // Sets default values for the lists to ensure empty lists can be deserialized + val a: List = emptyList(), + val b: List = emptyList() +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Data(listOf(1, 2, 3), listOf()) + val bytes = ProtoBuf.encodeToByteArray(data) + println(bytes.toAsciiHexString()) + // {08}{01}{08}{02}{08}{03} + println(ProtoBuf.decodeFromByteArray(bytes)) + // Data(a=[1, 2, 3], b=[]) +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* + +// Defines the data class with a oneof property +@OptIn(ExperimentalSerializationApi::class) +@Serializable +data class Data( + // Represents the name field with field number 1 + @ProtoNumber(1) val name: String, + // Uses the IPhoneType interface for the oneof phone group + @ProtoOneOf val phone: IPhoneType?, +) + +// The sealed interface representing the 'oneof' group +@Serializable sealed interface IPhoneType + +// Represents the home_phone field from the oneof group +@OptIn(ExperimentalSerializationApi::class) +@Serializable @JvmInline value class HomePhone(@ProtoNumber(2) val number: String): IPhoneType + +// Represents the work_phone field from the oneof group +@OptIn(ExperimentalSerializationApi::class) +@Serializable data class WorkPhone(@ProtoNumber(3) val number: String): IPhoneType + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val dataTom = Data("Tom", HomePhone("123")) + val stringTom = ProtoBuf.encodeToHexString(dataTom) + val dataJerry = Data("Jerry", WorkPhone("789")) + val stringJerry = ProtoBuf.encodeToHexString(dataJerry) + println(stringTom) + // 0a03546f6d1203313233 + println(stringJerry) + // 0a054a657272791a03373839 + println(ProtoBuf.decodeFromHexString(stringTom)) + // Data(name=Tom, phone=HomePhone(number=123)) + println(ProtoBuf.decodeFromHexString(stringJerry)) + // Data(name=Jerry, phone=WorkPhone(number=789)) +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.protobuf.* +import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator + +@Serializable +data class SampleData( + val amount: Long, + val description: String?, + val department: String = "QA" +) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val descriptors = listOf(SampleData.serializer().descriptor) + val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors) + println(schemas) +} + +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-serialization-properties:%serializationVersion%") +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.properties.Properties +import kotlinx.serialization.properties.* + +@Serializable +class Project(val name: String, val owner: User) + +@Serializable +class User(val name: String) + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + val data = Project("kotlinx.serialization", User("kotlin")) + // Encodes the data object into a map with dot-separated keys for nested properties + val map = Properties.encodeToMap(data) + // Iterates through the map and prints the key-value pairs + map.forEach { (k, v) -> println("$k = $v") } + // name = kotlinx.serialization + // owner.name = kotlin +} + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.descriptors.* +import kotlin.io.encoding.* + +@OptIn(ExperimentalEncodingApi::class) +// Custom serializer for converting ByteArray to Base64 format and back +object ByteArrayAsBase64Serializer : KSerializer { + private val base64 = Base64.Default + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor( + "ByteArrayAsBase64Serializer", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: ByteArray) { + val base64Encoded = base64.encode(value) + encoder.encodeString(base64Encoded) + } + + override fun deserialize(decoder: Decoder): ByteArray { + val base64Decoded = decoder.decodeString() + return base64.decode(base64Decoded) + } +} + +@Serializable +data class Value( + @Serializable(with = ByteArrayAsBase64Serializer::class) + val base64Input: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as Value + return base64Input.contentEquals(other.base64Input) + } + + override fun hashCode(): Int { + return base64Input.contentHashCode() + } +} + +fun main() { + val string = "foo string" + val value = Value(string.toByteArray()) + + // Encodes the data class to a Base64 string + val encoded = Json.encodeToString(value) + println(encoded) + // {"base64Input":"Zm9vIHN0cmluZw=="} + + // Decodes the Base64 string back to its original form + val decoded = Json.decodeFromString(encoded) + println(decoded.base64Input.decodeToString()) + // foo string } diff --git a/guide/example/example-formats-05.kt b/guide/example/example-formats-05.kt deleted file mode 100644 index 796e3eb079..0000000000 --- a/guide/example/example-formats-05.kt +++ /dev/null @@ -1,26 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats05 - -import kotlinx.serialization.* -import kotlinx.serialization.protobuf.* - -fun ByteArray.toAsciiHexString() = joinToString("") { - if (it in 32..127) it.toInt().toChar().toString() else - "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" -} - -@Serializable -data class Project( - @ProtoNumber(1) - val name: String, - @ProtoNumber(3) - val language: String -) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val bytes = ProtoBuf.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - val obj = ProtoBuf.decodeFromByteArray(bytes) - println(obj) -} diff --git a/guide/example/example-formats-06.kt b/guide/example/example-formats-06.kt deleted file mode 100644 index 5e4fbc5011..0000000000 --- a/guide/example/example-formats-06.kt +++ /dev/null @@ -1,25 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats06 - -import kotlinx.serialization.* -import kotlinx.serialization.protobuf.* - -fun ByteArray.toAsciiHexString() = joinToString("") { - if (it in 32..127) it.toInt().toChar().toString() else - "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" -} - -@Serializable -class Data( - @ProtoType(ProtoIntegerType.DEFAULT) - val a: Int, - @ProtoType(ProtoIntegerType.SIGNED) - val b: Int, - @ProtoType(ProtoIntegerType.FIXED) - val c: Int -) - -fun main() { - val data = Data(1, -2, 3) - println(ProtoBuf.encodeToByteArray(data).toAsciiHexString()) -} diff --git a/guide/example/example-formats-07.kt b/guide/example/example-formats-07.kt deleted file mode 100644 index 52bc826b79..0000000000 --- a/guide/example/example-formats-07.kt +++ /dev/null @@ -1,23 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats07 - -import kotlinx.serialization.* -import kotlinx.serialization.protobuf.* - -fun ByteArray.toAsciiHexString() = joinToString("") { - if (it in 32..127) it.toInt().toChar().toString() else - "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" -} - -@Serializable -data class Data( - val a: List = emptyList(), - val b: List = emptyList() -) - -fun main() { - val data = Data(listOf(1, 2, 3), listOf()) - val bytes = ProtoBuf.encodeToByteArray(data) - println(bytes.toAsciiHexString()) - println(ProtoBuf.decodeFromByteArray(bytes)) -} diff --git a/guide/example/example-formats-08.kt b/guide/example/example-formats-08.kt deleted file mode 100644 index 1316aa0d33..0000000000 --- a/guide/example/example-formats-08.kt +++ /dev/null @@ -1,32 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats08 - -import kotlinx.serialization.* -import kotlinx.serialization.protobuf.* - -// The outer class -@Serializable -data class Data( - @ProtoNumber(1) val name: String, - @ProtoOneOf val phone: IPhoneType?, -) - -// The oneof interface -@Serializable sealed interface IPhoneType - -// Message holder for home_phone -@Serializable @JvmInline value class HomePhone(@ProtoNumber(2) val number: String): IPhoneType - -// 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. -@Serializable data class WorkPhone(@ProtoNumber(3) val number: String): IPhoneType - -fun main() { - val dataTom = Data("Tom", HomePhone("123")) - val stringTom = ProtoBuf.encodeToHexString(dataTom) - val dataJerry = Data("Jerry", WorkPhone("789")) - val stringJerry = ProtoBuf.encodeToHexString(dataJerry) - println(stringTom) - println(stringJerry) - println(ProtoBuf.decodeFromHexString(stringTom)) - println(ProtoBuf.decodeFromHexString(stringJerry)) -} diff --git a/guide/example/example-formats-09.kt b/guide/example/example-formats-09.kt deleted file mode 100644 index 387061f084..0000000000 --- a/guide/example/example-formats-09.kt +++ /dev/null @@ -1,18 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats09 - -import kotlinx.serialization.* -import kotlinx.serialization.protobuf.* -import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator - -@Serializable -data class SampleData( - val amount: Long, - val description: String?, - val department: String = "QA" -) -fun main() { - val descriptors = listOf(SampleData.serializer().descriptor) - val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors) - println(schemas) -} diff --git a/guide/example/example-formats-10.kt b/guide/example/example-formats-10.kt deleted file mode 100644 index 020f175da9..0000000000 --- a/guide/example/example-formats-10.kt +++ /dev/null @@ -1,18 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats10 - -import kotlinx.serialization.* -import kotlinx.serialization.properties.Properties // todo: remove when no longer needed -import kotlinx.serialization.properties.* - -@Serializable -class Project(val name: String, val owner: User) - -@Serializable -class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin")) - val map = Properties.encodeToMap(data) - map.forEach { (k, v) -> println("$k = $v") } -} diff --git a/guide/example/example-formats-11.kt b/guide/example/example-formats-11.kt deleted file mode 100644 index 9fa4180d8e..0000000000 --- a/guide/example/example-formats-11.kt +++ /dev/null @@ -1,36 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats11 - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* - -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } -} - -fun encodeToList(serializer: SerializationStrategy, value: T): List { - val encoder = ListEncoder() - encoder.encodeSerializableValue(serializer, value) - return encoder.list -} - -inline fun encodeToList(value: T) = encodeToList(serializer(), value) - -@Serializable -data class Project(val name: String, val owner: User, val votes: Int) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin"), 9000) - println(encodeToList(data)) -} diff --git a/guide/example/example-formats-12.kt b/guide/example/example-formats-12.kt deleted file mode 100644 index 803f331e6c..0000000000 --- a/guide/example/example-formats-12.kt +++ /dev/null @@ -1,62 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats12 - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* - -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } -} - -fun encodeToList(serializer: SerializationStrategy, value: T): List { - val encoder = ListEncoder() - encoder.encodeSerializableValue(serializer, value) - return encoder.list -} - -inline fun encodeToList(value: T) = encodeToList(serializer(), value) - -class ListDecoder(val list: ArrayDeque) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list) -} - -fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { - val decoder = ListDecoder(ArrayDeque(list)) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) - -@Serializable -data class Project(val name: String, val owner: User, val votes: Int) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin"), 9000) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} diff --git a/guide/example/example-formats-13.kt b/guide/example/example-formats-13.kt deleted file mode 100644 index 568017765c..0000000000 --- a/guide/example/example-formats-13.kt +++ /dev/null @@ -1,64 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats13 - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* - -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } -} - -fun encodeToList(serializer: SerializationStrategy, value: T): List { - val encoder = ListEncoder() - encoder.encodeSerializableValue(serializer, value) - return encoder.list -} - -inline fun encodeToList(value: T) = encodeToList(serializer(), value) - -class ListDecoder(val list: ArrayDeque) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list) - - override fun decodeSequentially(): Boolean = true -} - -fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { - val decoder = ListDecoder(ArrayDeque(list)) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) - -@Serializable -data class Project(val name: String, val owner: User, val votes: Int) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin"), 9000) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} diff --git a/guide/example/example-formats-14.kt b/guide/example/example-formats-14.kt deleted file mode 100644 index 3f88d51caf..0000000000 --- a/guide/example/example-formats-14.kt +++ /dev/null @@ -1,72 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats14 - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* - -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } - - override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - encodeInt(collectionSize) - return this - } -} - -fun encodeToList(serializer: SerializationStrategy, value: T): List { - val encoder = ListEncoder() - encoder.encodeSerializableValue(serializer, value) - return encoder.list -} - -inline fun encodeToList(value: T) = encodeToList(serializer(), value) - -class ListDecoder(val list: ArrayDeque, var elementsCount: Int = 0) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list, descriptor.elementsCount) - - override fun decodeSequentially(): Boolean = true - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = - decodeInt().also { elementsCount = it } -} - -fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { - val decoder = ListDecoder(ArrayDeque(list)) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) - -@Serializable -data class Project(val name: String, val owners: List, val votes: Int) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", listOf(User("kotlin"), User("jetbrains")), 9000) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} diff --git a/guide/example/example-formats-15.kt b/guide/example/example-formats-15.kt deleted file mode 100644 index c48c18e4f1..0000000000 --- a/guide/example/example-formats-15.kt +++ /dev/null @@ -1,78 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats15 - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* - -class ListEncoder : AbstractEncoder() { - val list = mutableListOf() - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun encodeValue(value: Any) { - list.add(value) - } - - override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - encodeInt(collectionSize) - return this - } - - override fun encodeNull() = encodeValue("NULL") - override fun encodeNotNullMark() = encodeValue("!!") -} - -fun encodeToList(serializer: SerializationStrategy, value: T): List { - val encoder = ListEncoder() - encoder.encodeSerializableValue(serializer, value) - return encoder.list -} - -inline fun encodeToList(value: T) = encodeToList(serializer(), value) - -class ListDecoder(val list: ArrayDeque, var elementsCount: Int = 0) : AbstractDecoder() { - private var elementIndex = 0 - - override val serializersModule: SerializersModule = EmptySerializersModule() - - override fun decodeValue(): Any = list.removeFirst() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - ListDecoder(list, descriptor.elementsCount) - - override fun decodeSequentially(): Boolean = true - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = - decodeInt().also { elementsCount = it } - - override fun decodeNotNullMark(): Boolean = decodeString() != "NULL" -} - -fun decodeFromList(list: List, deserializer: DeserializationStrategy): T { - val decoder = ListDecoder(ArrayDeque(list)) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFromList(list: List): T = decodeFromList(list, serializer()) - -@Serializable -data class Project(val name: String, val owner: User?, val votes: Int?) - -@Serializable -data class User(val name: String) - -fun main() { - val data = Project("kotlinx.serialization", User("kotlin") , null) - val list = encodeToList(data) - println(list) - val obj = decodeFromList(list) - println(obj) -} - diff --git a/guide/example/example-formats-16.kt b/guide/example/example-formats-16.kt deleted file mode 100644 index 5bf0e44516..0000000000 --- a/guide/example/example-formats-16.kt +++ /dev/null @@ -1,94 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats16 - -import kotlinx.serialization.* -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* -import java.io.* - -class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() { - override val serializersModule: SerializersModule = EmptySerializersModule() - override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) - override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) - override fun encodeShort(value: Short) = output.writeShort(value.toInt()) - override fun encodeInt(value: Int) = output.writeInt(value) - override fun encodeLong(value: Long) = output.writeLong(value) - override fun encodeFloat(value: Float) = output.writeFloat(value) - override fun encodeDouble(value: Double) = output.writeDouble(value) - override fun encodeChar(value: Char) = output.writeChar(value.code) - override fun encodeString(value: String) = output.writeUTF(value) - override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index) - - override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - encodeInt(collectionSize) - return this - } - - override fun encodeNull() = encodeBoolean(false) - override fun encodeNotNullMark() = encodeBoolean(true) -} - -fun encodeTo(output: DataOutput, serializer: SerializationStrategy, value: T) { - val encoder = DataOutputEncoder(output) - encoder.encodeSerializableValue(serializer, value) -} - -inline fun encodeTo(output: DataOutput, value: T) = encodeTo(output, serializer(), value) - -class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() { - private var elementIndex = 0 - override val serializersModule: SerializersModule = EmptySerializersModule() - override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 - override fun decodeByte(): Byte = input.readByte() - override fun decodeShort(): Short = input.readShort() - override fun decodeInt(): Int = input.readInt() - override fun decodeLong(): Long = input.readLong() - override fun decodeFloat(): Float = input.readFloat() - override fun decodeDouble(): Double = input.readDouble() - override fun decodeChar(): Char = input.readChar() - override fun decodeString(): String = input.readUTF() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - DataInputDecoder(input, descriptor.elementsCount) - - override fun decodeSequentially(): Boolean = true - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = - decodeInt().also { elementsCount = it } - - override fun decodeNotNullMark(): Boolean = decodeBoolean() -} - -fun decodeFrom(input: DataInput, deserializer: DeserializationStrategy): T { - val decoder = DataInputDecoder(input) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFrom(input: DataInput): T = decodeFrom(input, serializer()) - -fun ByteArray.toAsciiHexString() = joinToString("") { - if (it in 32..127) it.toInt().toChar().toString() else - "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" -} - -@Serializable -data class Project(val name: String, val language: String) - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - val output = ByteArrayOutputStream() - encodeTo(DataOutputStream(output), data) - val bytes = output.toByteArray() - println(bytes.toAsciiHexString()) - val input = ByteArrayInputStream(bytes) - val obj = decodeFrom(DataInputStream(input)) - println(obj) -} diff --git a/guide/example/example-formats-17.kt b/guide/example/example-formats-17.kt deleted file mode 100644 index 9a1c5a21d3..0000000000 --- a/guide/example/example-formats-17.kt +++ /dev/null @@ -1,135 +0,0 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. -package example.exampleFormats17 - -import kotlinx.serialization.* -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.modules.* -import kotlinx.serialization.encoding.* -import java.io.* - -private val byteArraySerializer = serializer() -class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() { - override val serializersModule: SerializersModule = EmptySerializersModule() - override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0) - override fun encodeByte(value: Byte) = output.writeByte(value.toInt()) - override fun encodeShort(value: Short) = output.writeShort(value.toInt()) - override fun encodeInt(value: Int) = output.writeInt(value) - override fun encodeLong(value: Long) = output.writeLong(value) - override fun encodeFloat(value: Float) = output.writeFloat(value) - override fun encodeDouble(value: Double) = output.writeDouble(value) - override fun encodeChar(value: Char) = output.writeChar(value.code) - override fun encodeString(value: String) = output.writeUTF(value) - override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output.writeInt(index) - - override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - encodeInt(collectionSize) - return this - } - - override fun encodeNull() = encodeBoolean(false) - override fun encodeNotNullMark() = encodeBoolean(true) - - override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { - if (serializer.descriptor == byteArraySerializer.descriptor) - encodeByteArray(value as ByteArray) - else - super.encodeSerializableValue(serializer, value) - } - - private fun encodeByteArray(bytes: ByteArray) { - encodeCompactSize(bytes.size) - output.write(bytes) - } - - private fun encodeCompactSize(value: Int) { - if (value < 0xff) { - output.writeByte(value) - } else { - output.writeByte(0xff) - output.writeInt(value) - } - } -} - -fun encodeTo(output: DataOutput, serializer: SerializationStrategy, value: T) { - val encoder = DataOutputEncoder(output) - encoder.encodeSerializableValue(serializer, value) -} - -inline fun encodeTo(output: DataOutput, value: T) = encodeTo(output, serializer(), value) - -class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() { - private var elementIndex = 0 - override val serializersModule: SerializersModule = EmptySerializersModule() - override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 - override fun decodeByte(): Byte = input.readByte() - override fun decodeShort(): Short = input.readShort() - override fun decodeInt(): Int = input.readInt() - override fun decodeLong(): Long = input.readLong() - override fun decodeFloat(): Float = input.readFloat() - override fun decodeDouble(): Double = input.readDouble() - override fun decodeChar(): Char = input.readChar() - override fun decodeString(): String = input.readUTF() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt() - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE - return elementIndex++ - } - - override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = - DataInputDecoder(input, descriptor.elementsCount) - - override fun decodeSequentially(): Boolean = true - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = - decodeInt().also { elementsCount = it } - - override fun decodeNotNullMark(): Boolean = decodeBoolean() - - @Suppress("UNCHECKED_CAST") - override fun decodeSerializableValue(deserializer: DeserializationStrategy, previousValue: T?): T = - if (deserializer.descriptor == byteArraySerializer.descriptor) - decodeByteArray() as T - else - super.decodeSerializableValue(deserializer, previousValue) - - private fun decodeByteArray(): ByteArray { - val bytes = ByteArray(decodeCompactSize()) - input.readFully(bytes) - return bytes - } - - private fun decodeCompactSize(): Int { - val byte = input.readByte().toInt() and 0xff - if (byte < 0xff) return byte - return input.readInt() - } -} - -fun decodeFrom(input: DataInput, deserializer: DeserializationStrategy): T { - val decoder = DataInputDecoder(input) - return decoder.decodeSerializableValue(deserializer) -} - -inline fun decodeFrom(input: DataInput): T = decodeFrom(input, serializer()) - -fun ByteArray.toAsciiHexString() = joinToString("") { - if (it in 32..127) it.toInt().toChar().toString() else - "{${it.toUByte().toString(16).padStart(2, '0').uppercase()}}" -} - -@Serializable -data class Project(val name: String, val attachment: ByteArray) - -fun main() { - val data = Project("kotlinx.serialization", byteArrayOf(0x0A, 0x0B, 0x0C, 0x0D)) - val output = ByteArrayOutputStream() - encodeTo(DataOutputStream(output), data) - val bytes = output.toByteArray() - println(bytes.toAsciiHexString()) - val input = ByteArrayInputStream(bytes) - val obj = decodeFrom(DataInputStream(input)) - println(obj) -} diff --git a/guide/example/example-json-02.kt b/guide/example/example-json-02.kt index 41b1942d78..5c95f23add 100644 --- a/guide/example/example-json-02.kt +++ b/guide/example/example-json-02.kt @@ -4,6 +4,10 @@ package example.exampleJson02 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + val format = Json { isLenient = true } enum class Status { SUPPORTED } diff --git a/guide/example/example-json-03.kt b/guide/example/example-json-03.kt index 2772d3d1f9..23e0ce2b99 100644 --- a/guide/example/example-json-03.kt +++ b/guide/example/example-json-03.kt @@ -4,6 +4,10 @@ package example.exampleJson03 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + // Configures a Json instance to ignore unknown keys val format = Json { ignoreUnknownKeys = true } diff --git a/guide/example/example-json-04.kt b/guide/example/example-json-04.kt index 455f889db1..7d280647fb 100644 --- a/guide/example/example-json-04.kt +++ b/guide/example/example-json-04.kt @@ -4,17 +4,24 @@ package example.exampleJson04 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Configures a Json instance to encode default values +val format = Json { encodeDefaults = true } + @Serializable -// Maps both "name" and "title" JSON fields to the `name` property -data class Project(@JsonNames("title") val name: String) +class Project( + val name: String, + val language: String = "Kotlin", + val website: String? = null +) fun main() { - val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") - println(project) - // Project(name=kotlinx.serialization) + val data = Project("kotlinx.serialization") - val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") - // Both `name` and `title` Json fields correspond to `name` property - println(oldProject) - // Project(name=kotlinx.coroutines) + // Encodes all the property values including the default ones + println(format.encodeToString(data)) + // {"name":"kotlinx.serialization","language":"Kotlin","website":null} } diff --git a/guide/example/example-json-05.kt b/guide/example/example-json-05.kt index e51de28e8a..c473d3950d 100644 --- a/guide/example/example-json-05.kt +++ b/guide/example/example-json-05.kt @@ -4,20 +4,32 @@ package example.exampleJson05 import kotlinx.serialization.* import kotlinx.serialization.json.* -// Configures a Json instance to encode default values -val format = Json { encodeDefaults = true } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Configures a Json instance to omit null values during serialization +val format = Json { explicitNulls = false } @Serializable -class Project( +data class Project( val name: String, - val language: String = "Kotlin", - val website: String? = null + val language: String, + val version: String? = "1.2.2", + val website: String?, + val description: String? = null ) fun main() { - val data = Project("kotlinx.serialization") + val data = Project("kotlinx.serialization", "Kotlin", null, null, null) + val json = format.encodeToString(data) + + // The version, website, and description fields are omitted from the output JSON + println(json) + // {"name":"kotlinx.serialization","language":"Kotlin"} - // Encodes all the property values including the default ones - println(format.encodeToString(data)) - // {"name":"kotlinx.serialization","language":"Kotlin","website":null} + // Missing nullable fields without defaults are treated as null + // Fields with defaults are filled with their default values + println(format.decodeFromString(json)) + // Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null) } diff --git a/guide/example/example-json-06.kt b/guide/example/example-json-06.kt index d4a21388f8..ceb6a96354 100644 --- a/guide/example/example-json-06.kt +++ b/guide/example/example-json-06.kt @@ -4,28 +4,21 @@ package example.exampleJson06 import kotlinx.serialization.* import kotlinx.serialization.json.* -// Configures a Json instance to omit null values during serialization -val format = Json { explicitNulls = false } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +val format = Json { coerceInputValues = true } @Serializable -data class Project( - val name: String, - val language: String, - val version: String? = "1.2.2", - val website: String?, - val description: String? = null -) +data class Project(val name: String, val language: String = "Kotlin") fun main() { - val data = Project("kotlinx.serialization", "Kotlin", null, null, null) - val json = format.encodeToString(data) - - // The version, website, and description fields are omitted from the output JSON - println(json) - // {"name":"kotlinx.serialization","language":"Kotlin"} + val data = format.decodeFromString(""" + {"name":"kotlinx.serialization","language":null} + """) - // Missing nullable fields without defaults are treated as null - // Fields with defaults are filled with their default values - println(format.decodeFromString(json)) - // Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null) + // The invalid `null` value for `language` is coerced to its default value + println(data) + // Project(name=kotlinx.serialization, language=Kotlin) } diff --git a/guide/example/example-json-07.kt b/guide/example/example-json-07.kt index 9c584f8ca2..54cb0c4e18 100644 --- a/guide/example/example-json-07.kt +++ b/guide/example/example-json-07.kt @@ -4,17 +4,24 @@ package example.exampleJson07 import kotlinx.serialization.* import kotlinx.serialization.json.* -val format = Json { coerceInputValues = true } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +enum class Color { BLACK, WHITE } @Serializable -data class Project(val name: String, val language: String = "Kotlin") +data class Brush(val foreground: Color = Color.BLACK, val background: Color?) + +val json = Json { + coerceInputValues = true + explicitNulls = false +} fun main() { - val data = format.decodeFromString(""" - {"name":"kotlinx.serialization","language":null} - """) - // The invalid `null` value for `language` is coerced to its default value - println(data) - // Project(name=kotlinx.serialization, language=Kotlin) + // Decodes `foreground` to its default value and `background` to `null` + val brush = json.decodeFromString("""{"foreground":"pink", "background":"purple"}""") + println(brush) + // Brush(foreground=BLACK, background=null) } diff --git a/guide/example/example-json-08.kt b/guide/example/example-json-08.kt index 3e064b625e..c315745d30 100644 --- a/guide/example/example-json-08.kt +++ b/guide/example/example-json-08.kt @@ -4,20 +4,22 @@ package example.exampleJson08 import kotlinx.serialization.* import kotlinx.serialization.json.* -enum class Color { BLACK, WHITE } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* -@Serializable -data class Brush(val foreground: Color = Color.BLACK, val background: Color?) +val format = Json { allowStructuredMapKeys = true } -val json = Json { - coerceInputValues = true - explicitNulls = false -} +@Serializable +data class Project(val name: String) fun main() { - - // Decodes `foreground` to its default value and `background` to `null` - val brush = json.decodeFromString("""{"foreground":"pink", "background":"purple"}""") - println(brush) - // Brush(foreground=BLACK, background=null) + val map = mapOf( + Project("kotlinx.serialization") to "Serialization", + Project("kotlinx.coroutines") to "Coroutines" + ) + // Serializes the map with structured keys as a JSON array: + // [key1, value1, key2, value2,...] + println(format.encodeToString(map)) + // [{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"] } diff --git a/guide/example/example-json-09.kt b/guide/example/example-json-09.kt index d351f16436..510f5f11c2 100644 --- a/guide/example/example-json-09.kt +++ b/guide/example/example-json-09.kt @@ -4,18 +4,22 @@ package example.exampleJson09 import kotlinx.serialization.* import kotlinx.serialization.json.* -val format = Json { allowStructuredMapKeys = true } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Configures a Json instance to allow special floating-point values +val format = Json { allowSpecialFloatingPointValues = true } @Serializable -data class Project(val name: String) +class Data( + val value: Double +) fun main() { - val map = mapOf( - Project("kotlinx.serialization") to "Serialization", - Project("kotlinx.coroutines") to "Coroutines" - ) - // Serializes the map with structured keys as a JSON array: - // `[key1, value1, key2, value2,...]`. - println(format.encodeToString(map)) - // [{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"] + val data = Data(Double.NaN) + // This example produces the following non-standard JSON output, yet it is a widely used encoding for + // special values in JVM world: + println(format.encodeToString(data)) + // {"value":NaN} } diff --git a/guide/example/example-json-10.kt b/guide/example/example-json-10.kt index 408c86b3e8..4c8923c800 100644 --- a/guide/example/example-json-10.kt +++ b/guide/example/example-json-10.kt @@ -4,18 +4,37 @@ package example.exampleJson10 import kotlinx.serialization.* import kotlinx.serialization.json.* -// Configures a Json instance to allow special floating-point values -val format = Json { allowSpecialFloatingPointValues = true } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Configures a Json instance to use a custom class discriminator +val format = Json { classDiscriminator = "#class" } + +@Serializable +sealed class Project { + abstract val name: String +} + +// Specifies a custom serial name for the OwnedProject class +@Serializable +@SerialName("owned") +class OwnedProject(override val name: String, val owner: String) : Project() +// Specifies a custom serial name for the SimpleProject class @Serializable -class Data( - val value: Double -) +@SerialName("simple") +class SimpleProject(override val name: String) : Project() fun main() { - val data = Data(Double.NaN) - // This example produces the following non-standard JSON output, yet it is a widely used encoding for - // special values in JVM world: - println(format.encodeToString(data)) - // {"value":NaN} + val simpleProject: Project = SimpleProject("kotlinx.serialization") + val ownedProject: Project = OwnedProject("kotlinx.coroutines", "kotlin") + + // Serializes SimpleProject with #class: "simple" + println(format.encodeToString(simpleProject)) + // {"#class":"simple","name":"kotlinx.serialization"} + + // Serializes OwnedProject with #class: "owned" + println(format.encodeToString(ownedProject)) + // {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} } diff --git a/guide/example/example-json-11.kt b/guide/example/example-json-11.kt index bd7d8301d1..09b328431f 100644 --- a/guide/example/example-json-11.kt +++ b/guide/example/example-json-11.kt @@ -4,33 +4,36 @@ package example.exampleJson11 import kotlinx.serialization.* import kotlinx.serialization.json.* -// Configures a Json instance to use a custom class discriminator -val format = Json { classDiscriminator = "#class" } +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +// The @JsonClassDiscriminator annotation is inheritable, so all subclasses of Base will have the same discriminator @Serializable -sealed class Project { - abstract val name: String -} +@JsonClassDiscriminator("message_type") +sealed class Base -// Specifies a custom serial name for the OwnedProject class +// Class discriminator is inherited from Base @Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() +sealed class ErrorClass: Base() -// Specifies a custom serial name for the SimpleProject class +// Defines a class that combines a message and an optional error @Serializable -@SerialName("simple") -class SimpleProject(override val name: String) : Project() +data class Message(val message: Base, val error: ErrorClass?) -fun main() { - val simpleProject: Project = SimpleProject("kotlinx.serialization") - val ownedProject: Project = OwnedProject("kotlinx.coroutines", "kotlin") +@Serializable +@SerialName("my.app.BaseMessage") +data class BaseMessage(val message: String) : Base() - // Serializes SimpleProject with #class: "simple" - println(format.encodeToString(simpleProject)) - // {"#class":"simple","name":"kotlinx.serialization"} +@Serializable +@SerialName("my.app.GenericError") +data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() - // Serializes OwnedProject with #class: "owned" - println(format.encodeToString(ownedProject)) - // {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +val format = Json { classDiscriminator = "#class" } + +fun main() { + val data = Message(BaseMessage("not found"), GenericError(404)) + // The discriminator from the Base class is used + println(format.encodeToString(data)) + // {"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}} } diff --git a/guide/example/example-json-12.kt b/guide/example/example-json-12.kt index 7900660f74..1b7afa710e 100644 --- a/guide/example/example-json-12.kt +++ b/guide/example/example-json-12.kt @@ -4,32 +4,24 @@ package example.exampleJson12 import kotlinx.serialization.* import kotlinx.serialization.json.* -// The @JsonClassDiscriminator annotation is inheritable, so all subclasses of `Base` will have the same discriminator -@Serializable -@JsonClassDiscriminator("message_type") -sealed class Base - -// Class discriminator is inherited from Base -@Serializable -sealed class ErrorClass: Base() +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* -// Defines a class that combines a message and an optional error -@Serializable -data class Message(val message: Base, val error: ErrorClass?) +// Configures a Json instance to omit the class discriminator from the output +val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } @Serializable -@SerialName("my.app.BaseMessage") -data class BaseMessage(val message: String) : Base() +sealed class Project { + abstract val name: String +} @Serializable -@SerialName("my.app.GenericError") -data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() - -val format = Json { classDiscriminator = "#class" } +class OwnedProject(override val name: String, val owner: String) : Project() fun main() { - val data = Message(BaseMessage("not found"), GenericError(404)) - // The discriminator from the `Base` class is used + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes without a discriminator println(format.encodeToString(data)) - // {"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}} + // {"name":"kotlinx.coroutines","owner":"kotlin"} } diff --git a/guide/example/example-json-13.kt b/guide/example/example-json-13.kt index e284154620..4fe4627b44 100644 --- a/guide/example/example-json-13.kt +++ b/guide/example/example-json-13.kt @@ -4,20 +4,21 @@ package example.exampleJson13 import kotlinx.serialization.* import kotlinx.serialization.json.* -// Configures a Json instance to omit the class discriminator from the output -val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } - -@Serializable -sealed class Project { - abstract val name: String -} +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* @Serializable -class OwnedProject(override val name: String, val owner: String) : Project() +// Maps both name and title JSON fields to the name property +data class Project(@JsonNames("title") val name: String) fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - // Serializes without a discriminator - println(format.encodeToString(data)) - // {"name":"kotlinx.coroutines","owner":"kotlin"} + val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") + println(project) + // Project(name=kotlinx.serialization) + + val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") + // Both name and title Json fields correspond to name property + println(oldProject) + // Project(name=kotlinx.coroutines) } diff --git a/guide/example/example-json-14.kt b/guide/example/example-json-14.kt index fd72d582a4..0476b6b8c0 100644 --- a/guide/example/example-json-14.kt +++ b/guide/example/example-json-14.kt @@ -4,6 +4,11 @@ package example.exampleJson14 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Configures a Json instance to decode enum values in a case-insensitive way val format = Json { decodeEnumsCaseInsensitive = true } enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } @@ -12,5 +17,7 @@ enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } data class CasesList(val cases: List) fun main() { - println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) + // Decodes enum values regardless of their case, affecting both serial names and alternative names + println(format.decodeFromString("""{"cases":["value_A", "alternative"]}""")) + // CasesList(cases=[VALUE_A, VALUE_B]) } diff --git a/guide/example/example-json-15.kt b/guide/example/example-json-15.kt index e3ceabe8c7..c69913f67a 100644 --- a/guide/example/example-json-15.kt +++ b/guide/example/example-json-15.kt @@ -4,12 +4,19 @@ package example.exampleJson15 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + @Serializable data class Project(val projectName: String, val projectOwner: String) +// Configures a Json instance to apply SnakeCase naming strategy val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase } fun main() { val project = format.decodeFromString("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""") + // Serializes and deserializes as if all serial names are transformed from camel case to snake case println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) + // {"project_name":"kotlinx.serialization","project_owner":"Kotlin"} } diff --git a/guide/example/example-json-16.kt b/guide/example/example-json-16.kt deleted file mode 100644 index b7ed045b7b..0000000000 --- a/guide/example/example-json-16.kt +++ /dev/null @@ -1,57 +0,0 @@ -// This file was automatically generated from serialization-json-configuration.md by Knit tool. Do not edit. -package example.exampleJson16 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.descriptors.* -import kotlin.io.encoding.* - -@OptIn(ExperimentalEncodingApi::class) -object ByteArrayAsBase64Serializer : KSerializer { - private val base64 = Base64.Default - - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor( - "ByteArrayAsBase64Serializer", - PrimitiveKind.STRING - ) - - override fun serialize(encoder: Encoder, value: ByteArray) { - val base64Encoded = base64.encode(value) - encoder.encodeString(base64Encoded) - } - - override fun deserialize(decoder: Decoder): ByteArray { - val base64Decoded = decoder.decodeString() - return base64.decode(base64Decoded) - } -} - -@Serializable -data class Value( - @Serializable(with = ByteArrayAsBase64Serializer::class) - val base64Input: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - other as Value - return base64Input.contentEquals(other.base64Input) - } - - override fun hashCode(): Int { - return base64Input.contentHashCode() - } -} - -fun main() { - val string = "foo string" - val value = Value(string.toByteArray()) - val encoded = Json.encodeToString(value) - println(encoded) - val decoded = Json.decodeFromString(encoded) - println(decoded.base64Input.decodeToString()) -} diff --git a/guide/example/example-json-elements-01.kt b/guide/example/example-json-elements-01.kt index 9aa20fde0f..38b2d20b91 100644 --- a/guide/example/example-json-elements-01.kt +++ b/guide/example/example-json-elements-01.kt @@ -4,9 +4,18 @@ package example.exampleJsonElements01 import kotlinx.serialization.* import kotlinx.serialization.json.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + fun main() { val element = Json.parseToJsonElement(""" {"name":"kotlinx.serialization","language":"Kotlin"} """) + // Prints the `JsonElement` as a valid JSON string println(element) + // {"name":"kotlinx.serialization","language":"Kotlin"} } diff --git a/guide/example/example-json-elements-02.kt b/guide/example/example-json-elements-02.kt index e18bc73cf1..9cc5269814 100644 --- a/guide/example/example-json-elements-02.kt +++ b/guide/example/example-json-elements-02.kt @@ -4,6 +4,10 @@ package example.exampleJsonElements02 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + fun main() { val element = Json.parseToJsonElement(""" { @@ -11,8 +15,13 @@ fun main() { "forks": [{"votes": 42}, {"votes": 9000}, {}] } """) + // Sums `votes` in all objects in the `forks` array, ignoring the objects without `votes` val sum = element + // Accesses the "forks" key from the root JsonObject .jsonObject["forks"]!! + + // Checks that "forks" is a JsonArray and sums the "votes" from each JsonObject .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 } println(sum) + // 9042 } diff --git a/guide/example/example-json-elements-03.kt b/guide/example/example-json-elements-03.kt index 6b70dbc7ad..294de04dc2 100644 --- a/guide/example/example-json-elements-03.kt +++ b/guide/example/example-json-elements-03.kt @@ -4,13 +4,21 @@ package example.exampleJsonElements03 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + fun main() { val element = buildJsonObject { + // Adds a simple key-value pair to the JsonObject put("name", "kotlinx.serialization") + // Adds a nested JsonObject under the "owner" key putJsonObject("owner") { put("name", "kotlin") } + // Adds a JsonArray with multiple JsonObjects putJsonArray("forks") { + // Adds a JsonObject to the JsonArray addJsonObject { put("votes", 42) } @@ -19,5 +27,7 @@ fun main() { } } } + // Prints the resulting JSON string println(element) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} } diff --git a/guide/example/example-json-elements-04.kt b/guide/example/example-json-elements-04.kt index 164f24d887..a99dd110e2 100644 --- a/guide/example/example-json-elements-04.kt +++ b/guide/example/example-json-elements-04.kt @@ -4,6 +4,10 @@ package example.exampleJsonElements04 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + @Serializable data class Project(val name: String, val language: String) @@ -12,6 +16,9 @@ fun main() { put("name", "kotlinx.serialization") put("language", "Kotlin") } + + // Decodes the JsonElement into a Project object val data = Json.decodeFromJsonElement(element) println(data) + // Project(name=kotlinx.serialization, language=Kotlin) } diff --git a/guide/example/example-json-elements-05.kt b/guide/example/example-json-elements-05.kt index c40d92a2df..a966a73931 100644 --- a/guide/example/example-json-elements-05.kt +++ b/guide/example/example-json-elements-05.kt @@ -4,6 +4,9 @@ package example.exampleJsonElements05 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* import java.math.BigDecimal val format = Json { prettyPrint = true } @@ -11,7 +14,9 @@ val format = Json { prettyPrint = true } fun main() { val pi = BigDecimal("3.141592653589793238462643383279") + // Converts the BigDecimal to a Double, causing potential truncation val piJsonDouble = JsonPrimitive(pi.toDouble()) + // Converts the BigDecimal to a String, preserving the precision but treating it as a string in JSON val piJsonString = JsonPrimitive(pi.toString()) val piObject = buildJsonObject { @@ -20,4 +25,6 @@ fun main() { } println(format.encodeToString(piObject)) + // "pi_double": 3.141592653589793, + // "pi_string": "3.141592653589793238462643383279" } diff --git a/guide/example/example-json-elements-06.kt b/guide/example/example-json-elements-06.kt index 3fa045164c..df33efd761 100644 --- a/guide/example/example-json-elements-06.kt +++ b/guide/example/example-json-elements-06.kt @@ -4,6 +4,9 @@ package example.exampleJsonElements06 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* import java.math.BigDecimal val format = Json { prettyPrint = true } @@ -11,17 +14,23 @@ val format = Json { prettyPrint = true } fun main() { val pi = BigDecimal("3.141592653589793238462643383279") - // use JsonUnquotedLiteral to encode raw JSON content + // Encodes the raw JSON content using JsonUnquotedLiteral + @OptIn(ExperimentalSerializationApi::class) val piJsonLiteral = JsonUnquotedLiteral(pi.toString()) + // Converts to Double and String val piJsonDouble = JsonPrimitive(pi.toDouble()) val piJsonString = JsonPrimitive(pi.toString()) - + val piObject = buildJsonObject { put("pi_literal", piJsonLiteral) put("pi_double", piJsonDouble) put("pi_string", piJsonString) } + // `pi_literal` now accurately matches the value defined. println(format.encodeToString(piObject)) + // "pi_literal": 3.141592653589793238462643383279, + // "pi_double": 3.141592653589793, + // "pi_string": "3.141592653589793238462643383279" } diff --git a/guide/example/example-json-elements-07.kt b/guide/example/example-json-elements-07.kt index 6cd68623d9..bde09e348c 100644 --- a/guide/example/example-json-elements-07.kt +++ b/guide/example/example-json-elements-07.kt @@ -4,6 +4,9 @@ package example.exampleJsonElements07 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* import java.math.BigDecimal fun main() { @@ -12,12 +15,16 @@ fun main() { "pi_literal": 3.141592653589793238462643383279 } """.trimIndent() - + + // Decodes the JSON string into a JsonObject val piObject: JsonObject = Json.decodeFromString(piObjectJson) - + + // Extracts the string content from the JsonPrimitive val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content - + + // Converts the string to a BigDecimal val pi = BigDecimal(piJsonLiteral) - + // Prints the decoded value of pi, preserving all 30 decimal places println(pi) + // 3.141592653589793238462643383279 } diff --git a/guide/example/example-json-elements-08.kt b/guide/example/example-json-elements-08.kt index e3cb6c57b0..71f6e750e6 100644 --- a/guide/example/example-json-elements-08.kt +++ b/guide/example/example-json-elements-08.kt @@ -4,7 +4,13 @@ package example.exampleJsonElements08 import kotlinx.serialization.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@OptIn(ExperimentalSerializationApi::class) fun main() { - // caution: creating null with JsonUnquotedLiteral will cause an exception! + // Caution: creating null with JsonUnquotedLiteral causes an exception! JsonUnquotedLiteral("null") + // Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException } diff --git a/guide/example/example-json-elements-09.kt b/guide/example/example-json-elements-09.kt new file mode 100644 index 0000000000..6cf79f78b5 --- /dev/null +++ b/guide/example/example-json-elements-09.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from serialization-json-elements.md by Knit tool. Do not edit. +package example.exampleJsonElements09 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +fun main() { + val possiblyNull = JsonNull + + println(possiblyNull) + // null +} diff --git a/guide/example/example-json-transform-01.kt b/guide/example/example-json-transform-01.kt index 0c0519d562..20a784092f 100644 --- a/guide/example/example-json-transform-01.kt +++ b/guide/example/example-json-transform-01.kt @@ -5,6 +5,12 @@ import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* + +// Uses UserListSerializer to handle the serialization of the users property @Serializable data class Project( val name: String, @@ -12,20 +18,27 @@ data class Project( val users: List ) +// Defines the User data class @Serializable data class User(val name: String) +// Implements a custom serializer that wraps single objects into arrays during deserialization object UserListSerializer : JsonTransformingSerializer>(ListSerializer(User.serializer())) { - // If response is not an array, then it is a single object that should be wrapped into the array + // If response is not an array, then it is a single object that should be wrapped in an array override fun transformDeserialize(element: JsonElement): JsonElement = if (element !is JsonArray) JsonArray(listOf(element)) else element } fun main() { + // Deserializes a single JSON object wrapped as an array println(Json.decodeFromString(""" {"name":"kotlinx.serialization","users":{"name":"kotlin"}} """)) + // Project(name=kotlinx.serialization, users=[User(name=kotlin)]) + + // Deserializes a JSON array of objects println(Json.decodeFromString(""" {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]} """)) + // Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) } diff --git a/guide/example/example-json-transform-02.kt b/guide/example/example-json-transform-02.kt index 3d912faf48..dd456179ad 100644 --- a/guide/example/example-json-transform-02.kt +++ b/guide/example/example-json-transform-02.kt @@ -1,31 +1,34 @@ // This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. package example.exampleJsonTransform02 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* -import kotlinx.serialization.builtins.* - @Serializable data class Project( - val name: String, - @Serializable(with = UserListSerializer::class) - val users: List + val name: String, + @Serializable(with = UserListSerializer::class) + val users: List ) @Serializable data class User(val name: String) +// Unwraps single-element lists into a single object during serialization object UserListSerializer : JsonTransformingSerializer>(ListSerializer(User.serializer())) { override fun transformSerialize(element: JsonElement): JsonElement { - require(element is JsonArray) // this serializer is used only with lists + // Ensures that the input is a list + require(element is JsonArray) + // Unwraps single-element lists into a single JSON object return element.singleOrNull() ?: element } } - + fun main() { val data = Project("kotlinx.serialization", listOf(User("kotlin"))) println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","users":{"name":"kotlin"}} } diff --git a/guide/example/example-json-transform-03.kt b/guide/example/example-json-transform-03.kt index d61ac06b05..2b02317d87 100644 --- a/guide/example/example-json-transform-03.kt +++ b/guide/example/example-json-transform-03.kt @@ -1,6 +1,7 @@ // This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. package example.exampleJsonTransform03 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* @@ -8,9 +9,10 @@ import kotlinx.serialization.json.* @Serializable class Project(val name: String, val language: String) +// Custom serializer that omits the "language" property if it is equal to "Kotlin" object ProjectSerializer : JsonTransformingSerializer(Project.serializer()) { override fun transformSerialize(element: JsonElement): JsonElement = - // Filter out top-level key value pair with the key "language" and the value "Kotlin" + // Omits the "language" property if its value is "Kotlin" JsonObject(element.jsonObject.filterNot { (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin" }) @@ -18,6 +20,9 @@ object ProjectSerializer : JsonTransformingSerializer(Project.serialize fun main() { val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(data)) // using plugin-generated serializer + // Uses the plugin-generated serializer + println(Json.encodeToString(data)) + // {"name":"kotlinx.serialization","language":"Kotlin"} println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer + // {"name":"kotlinx.serialization"} } diff --git a/guide/example/example-json-transform-04.kt b/guide/example/example-json-transform-04.kt index 3384857bbe..42b04c0306 100644 --- a/guide/example/example-json-transform-04.kt +++ b/guide/example/example-json-transform-04.kt @@ -1,12 +1,11 @@ // This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. package example.exampleJsonTransform04 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* -import kotlinx.serialization.builtins.* - @Serializable abstract class Project { abstract val name: String @@ -19,8 +18,10 @@ data class BasicProject(override val name: String): Project() @Serializable data class OwnedProject(override val name: String, val owner: String) : Project() +// Custom serializer that selects deserializer based on the presence of "owner" object ProjectSerializer : JsonContentPolymorphicSerializer(Project::class) { override fun selectDeserializer(element: JsonElement) = when { + // Distinguishes the BasicProject and OwnedProject subclasses by the presence of "owner" key "owner" in element.jsonObject -> OwnedProject.serializer() else -> BasicProject.serializer() } @@ -32,6 +33,9 @@ fun main() { BasicProject("example") ) val string = Json.encodeToString(ListSerializer(ProjectSerializer), data) + // No class discriminator is added in the JSON output println(string) + // [{"name":"kotlinx.serialization","owner":"kotlin"},{"name":"example"}] println(Json.decodeFromString(ListSerializer(ProjectSerializer), string)) + // [OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)] } diff --git a/guide/example/example-json-transform-05.kt b/guide/example/example-json-transform-05.kt index 8064739f13..3a7ead834b 100644 --- a/guide/example/example-json-transform-05.kt +++ b/guide/example/example-json-transform-05.kt @@ -1,19 +1,21 @@ // This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. package example.exampleJsonTransform05 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* - import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* +// Defines a sealed class for API responses @Serializable(with = ResponseSerializer::class) sealed class Response { data class Ok(val data: T) : Response() data class Error(val message: String) : Response() } +// Implements custom serialization logic for Response class class ResponseSerializer(private val dataSerializer: KSerializer) : KSerializer> { override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) { element("Ok", dataSerializer.descriptor) @@ -22,9 +24,11 @@ class ResponseSerializer(private val dataSerializer: KSerializer) : KSeria }) } + // Deserializes Response from JSON override fun deserialize(decoder: Decoder): Response { // Decoder -> JsonDecoder - require(decoder is JsonDecoder) // this class can be decoded only by Json + // Ensures the decoder is a JsonDecoder + require(decoder is JsonDecoder) // JsonDecoder -> JsonElement val element = decoder.decodeJsonElement() // JsonElement -> value @@ -33,9 +37,11 @@ class ResponseSerializer(private val dataSerializer: KSerializer) : KSeria return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element)) } + // Serializes Response to JSON override fun serialize(encoder: Encoder, value: Response) { // Encoder -> JsonEncoder - require(encoder is JsonEncoder) // This class can be encoded only by Json + // Ensures the encoder is a JsonEncoder + require(encoder is JsonEncoder) // value -> JsonElement val element = when (value) { is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data) @@ -56,5 +62,7 @@ fun main() { ) val string = Json.encodeToString(responses) println(string) + // [{"name":"kotlinx.serialization"},{"error":"Not found"}] println(Json.decodeFromString>>(string)) + // [Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)] } diff --git a/guide/example/example-json-transform-06.kt b/guide/example/example-json-transform-06.kt index 6a2b0018bb..b8f5b236cf 100644 --- a/guide/example/example-json-transform-06.kt +++ b/guide/example/example-json-transform-06.kt @@ -1,10 +1,10 @@ // This file was automatically generated from serialization-transform-json.md by Knit tool. Do not edit. package example.exampleJsonTransform06 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* - import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* @@ -17,12 +17,16 @@ object UnknownProjectSerializer : KSerializer { } override fun deserialize(decoder: Decoder): UnknownProject { - // Cast to JSON-specific interface + // Ensures the decoder is JSON-specific val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") - // Read the whole content as JSON + + // Reads the entire content as JSON val json = jsonInput.decodeJsonElement().jsonObject - // Extract and remove name property + + // Extracts and removes the "name" property val name = json.getValue("name").jsonPrimitive.content + + // Flattens the remaining properties into the 'details' field val details = json.toMutableMap() details.remove("name") return UnknownProject(name, JsonObject(details)) @@ -34,5 +38,8 @@ object UnknownProjectSerializer : KSerializer { } fun main() { + // Deserializes JSON with unknown fields into 'UnknownProject' println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}""")) + // UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"}) + } diff --git a/guide/example/example-poly-01.kt b/guide/example/example-poly-01.kt index 80dbab158d..b837367901 100644 --- a/guide/example/example-poly-01.kt +++ b/guide/example/example-poly-01.kt @@ -1,15 +1,20 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly01 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* +// Defines an open class Project with a name property @Serializable open class Project(val name: String) +// Defines a derived class OwnedProject with an additional owner property class OwnedProject(name: String, val owner: String) : Project(name) fun main() { val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes data based on the static type Project, ignoring the OwnedProject properties println(Json.encodeToString(data)) -} + // {"name":"kotlinx.coroutines"} +} diff --git a/guide/example/example-poly-02.kt b/guide/example/example-poly-02.kt index a5bc2f9d2c..4e901ed339 100644 --- a/guide/example/example-poly-02.kt +++ b/guide/example/example-poly-02.kt @@ -1,15 +1,19 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly02 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -open class Project(val name: String) +abstract class Project { + abstract val name: String +} -class OwnedProject(name: String, val owner: String) : Project(name) +class OwnedProject(override val name: String, val owner: String) : Project() fun main() { - val data = OwnedProject("kotlinx.coroutines", "kotlin") + val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") println(Json.encodeToString(data)) -} + // Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'. +} diff --git a/guide/example/example-poly-03.kt b/guide/example/example-poly-03.kt index d39cabd699..ddf2f4706c 100644 --- a/guide/example/example-poly-03.kt +++ b/guide/example/example-poly-03.kt @@ -1,17 +1,22 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly03 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable -abstract class Project { +sealed class Project { abstract val name: String } +@Serializable class OwnedProject(override val name: String, val owner: String) : Project() +// Serializes data of compile-time type Project fun main() { val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // A `type` key is added to the resulting JSON object as a discriminator. println(Json.encodeToString(data)) -} + // {"type":"OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-04.kt b/guide/example/example-poly-04.kt index 1cef6aa05c..e0d8e2ae79 100644 --- a/guide/example/example-poly-04.kt +++ b/guide/example/example-poly-04.kt @@ -1,6 +1,7 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly04 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @@ -13,6 +14,10 @@ sealed class Project { class OwnedProject(override val name: String, val owner: String) : Project() fun main() { - val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") - println(Json.encodeToString(data)) // Serializing data of compile-time type Project -} + // The static type is OwnedProject + val data = OwnedProject("kotlinx.coroutines", "kotlin") + + // The `type` discriminator is not included because the static type is OwnedProject. + println(Json.encodeToString(data)) + // {"name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-05.kt b/guide/example/example-poly-05.kt index 48724d00a9..719d7c1ff6 100644 --- a/guide/example/example-poly-05.kt +++ b/guide/example/example-poly-05.kt @@ -1,6 +1,7 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly05 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @@ -13,6 +14,10 @@ sealed class Project { class OwnedProject(override val name: String, val owner: String) : Project() fun main() { - val data = OwnedProject("kotlinx.coroutines", "kotlin") // data: OwnedProject here - println(Json.encodeToString(data)) // Serializing data of compile-time type OwnedProject -} + // Sets the static type as OwnedProject + val data = OwnedProject("kotlinx.coroutines", "kotlin") + + // Specifies the base type Project, which includes the `type` discriminator in the output. + println(Json.encodeToString(data)) + // {"type":"OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-06.kt b/guide/example/example-poly-06.kt index c54136c6e7..27d464e1e6 100644 --- a/guide/example/example-poly-06.kt +++ b/guide/example/example-poly-06.kt @@ -1,6 +1,7 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly06 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @@ -8,12 +9,15 @@ import kotlinx.serialization.json.* sealed class Project { abstract val name: String } - + +// Assigns the custom serial name "owned" to OwnedProject for JSON output @Serializable @SerialName("owned") class OwnedProject(override val name: String, val owner: String) : Project() fun main() { val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes the object with the custom `type` key "owned" instead of the class name println(Json.encodeToString(data)) -} + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-07.kt b/guide/example/example-poly-07.kt index 7edd231cc0..7de4e7e09c 100644 --- a/guide/example/example-poly-07.kt +++ b/guide/example/example-poly-07.kt @@ -1,12 +1,14 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly07 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable sealed class Project { - abstract val name: String + abstract val name: String + // Defines a property with a backing field in the base class var status = "open" } @@ -15,7 +17,10 @@ sealed class Project { class OwnedProject(override val name: String, val owner: String) : Project() fun main() { - val json = Json { encodeDefaults = true } // "status" will be skipped otherwise + // Includes default values like "status" + val json = Json { encodeDefaults = true } val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes superclass properties before subclass properties println(json.encodeToString(data)) -} + // {"type":"owned","status":"open","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-08.kt b/guide/example/example-poly-08.kt index ab99810cb8..68eaeed79a 100644 --- a/guide/example/example-poly-08.kt +++ b/guide/example/example-poly-08.kt @@ -1,19 +1,24 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly08 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable sealed class Response - + +// Declares an object that extends Response @Serializable object EmptyResponse : Response() +// Declares a class that extends Response @Serializable -class TextResponse(val text: String) : Response() +class TextResponse(val text: String) : Response() +// Serializes a list of different responses fun main() { val list = listOf(EmptyResponse, TextResponse("OK")) println(Json.encodeToString(list)) -} + // [{"type":"EmptyResponse"},{"type":"TextResponse","text":"OK"}] +} diff --git a/guide/example/example-poly-09.kt b/guide/example/example-poly-09.kt index bc52c33d65..c94e8fd908 100644 --- a/guide/example/example-poly-09.kt +++ b/guide/example/example-poly-09.kt @@ -1,29 +1,36 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly09 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* +// Defines a SerializersModule with polymorphic serialization val module = SerializersModule { polymorphic(Project::class) { + // Registers OwnedProject as a subclass of Project subclass(OwnedProject::class) } } +// Creates a custom JSON format with the module val format = Json { serializersModule = module } +// Defines an abstract serializable class Project with an abstract property `name` @Serializable abstract class Project { abstract val name: String } - + +// Defines a subclass OwnedProject with an additional property `owner` and a SerialName @Serializable @SerialName("owned") class OwnedProject(override val name: String, val owner: String) : Project() fun main() { val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") + // Serializes data using the custom format println(format.encodeToString(data)) -} + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-10.kt b/guide/example/example-poly-10.kt index 4a4bedbe2a..4ea244b9f1 100644 --- a/guide/example/example-poly-10.kt +++ b/guide/example/example-poly-10.kt @@ -1,11 +1,12 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly10 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* +// Creates a SerializersModule to register the implementing classes of the interface val module = SerializersModule { polymorphic(Project::class) { subclass(OwnedProject::class) @@ -14,15 +15,19 @@ val module = SerializersModule { val format = Json { serializersModule = module } +// Declares an interface used for polymorphic serialization interface Project { val name: String } +// OwnedProject implements the Project interface @Serializable @SerialName("owned") class OwnedProject(override val name: String, val owner: String) : Project fun main() { + // Declares `data` with the type of `Project`, which is assigned an instance of `OwnedProject` val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") println(format.encodeToString(data)) -} + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-11.kt b/guide/example/example-poly-11.kt index 30c3c4d7ce..f6d7b7e63b 100644 --- a/guide/example/example-poly-11.kt +++ b/guide/example/example-poly-11.kt @@ -1,9 +1,9 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly11 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* val module = SerializersModule { @@ -22,10 +22,12 @@ interface Project { @SerialName("owned") class OwnedProject(override val name: String, val owner: String) : Project +// Data class contains a property of type Project, which is an interface @Serializable class Data(val project: Project) // Project is an interface fun main() { val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) println(format.encodeToString(data)) -} + // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} +} diff --git a/guide/example/example-poly-12.kt b/guide/example/example-poly-12.kt index 5006cb4d5f..ad546841af 100644 --- a/guide/example/example-poly-12.kt +++ b/guide/example/example-poly-12.kt @@ -1,13 +1,14 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly12 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* val module = SerializersModule { - polymorphic(Project::class) { + // Registers OwnedProject as a subclass of Any for polymorphic serialization + polymorphic(Any::class) { subclass(OwnedProject::class) } } @@ -18,12 +19,15 @@ val format = Json { serializersModule = module } abstract class Project { abstract val name: String } - + @Serializable @SerialName("owned") class OwnedProject(override val name: String, val owner: String) : Project() fun main() { + // Declares data as Any, requiring explicit handling of polymorphism val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(data)) -} + // Uses PolymorphicSerializer to serialize data of type Any + println(format.encodeToString(PolymorphicSerializer(Any::class), data)) + // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} +} diff --git a/guide/example/example-poly-13.kt b/guide/example/example-poly-13.kt index e79de66f9a..3e821c8d0d 100644 --- a/guide/example/example-poly-13.kt +++ b/guide/example/example-poly-13.kt @@ -1,9 +1,9 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly13 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* val module = SerializersModule { @@ -11,18 +11,26 @@ val module = SerializersModule { subclass(OwnedProject::class) } } + val format = Json { serializersModule = module } -@Serializable -abstract class Project { - abstract val name: String +interface Project { + val name: String } - + @Serializable @SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() +class OwnedProject(override val name: String, val owner: String) : Project + +@Serializable +class Data( + // Ensures the project property is serialized using PolymorphicSerializer + @Polymorphic + val project: Any +) fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") + val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) println(format.encodeToString(data)) -} + // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} +} diff --git a/guide/example/example-poly-14.kt b/guide/example/example-poly-14.kt index eea74517f4..36e316cb8d 100644 --- a/guide/example/example-poly-14.kt +++ b/guide/example/example-poly-14.kt @@ -1,29 +1,41 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly14 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* +import kotlin.reflect.KClass val module = SerializersModule { - polymorphic(Any::class) { + // Creates a function to register subclasses for each superclass + fun PolymorphicModuleBuilder.registerProjectSubclasses() { subclass(OwnedProject::class) } + // Applies the subclass registration to Any and Project + polymorphic(Any::class) { registerProjectSubclasses() } + polymorphic(Project::class) { registerProjectSubclasses() } } val format = Json { serializersModule = module } -@Serializable -abstract class Project { - abstract val name: String +interface Project { + val name: String } - + @Serializable @SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project() +class OwnedProject(override val name: String, val owner: String) : Project + +@Serializable +class Data( + val project: Project, + @Polymorphic val any: Any +) fun main() { - val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") - println(format.encodeToString(PolymorphicSerializer(Any::class), data)) -} + val project = OwnedProject("kotlinx.coroutines", "kotlin") + val data = Data(project, project) + println(format.encodeToString(data)) + // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"},"any":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} +} diff --git a/guide/example/example-poly-15.kt b/guide/example/example-poly-15.kt index e191b8f6c6..897048c2ca 100644 --- a/guide/example/example-poly-15.kt +++ b/guide/example/example-poly-15.kt @@ -1,34 +1,61 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly15 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* -val module = SerializersModule { - polymorphic(Any::class) { - subclass(OwnedProject::class) - } -} +// Defines an abstract response class with a generic parameter T +@Serializable +abstract class Response -val format = Json { serializersModule = module } +// Represents a successful response with a generic data type +@Serializable +@SerialName("OkResponse") +data class OkResponse(val data: T) : Response() -interface Project { - val name: String +// Defines the abstract class Project +@Serializable +abstract class Project { + abstract val name: String } +// Concrete subclass of Project @Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project +@SerialName("OwnedProject") +data class OwnedProject(override val name: String, val owner: String) : Project() + +// Defines a serializers module for polymorphic classes +val responseModule = SerializersModule { + polymorphic(Response::class) { + // Registers the polymorphic serializer for OkResponse + subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) + } + polymorphic(Any::class) { + // Registers OwnedProject as a subclass of Any + subclass(OwnedProject::class) + } + polymorphic(Project::class) { + // Registers OwnedProject as a subclass of Project + subclass(OwnedProject::class) + } +} -@Serializable -class Data( - @Polymorphic // the code does not compile without it - val project: Any -) +// Creates a Json format with the registered serializers +val format = Json { serializersModule = responseModule } fun main() { - val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) - println(format.encodeToString(data)) + // Creates an instance of OkResponse with a Project subtype + val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) + + // Serializes the data to JSON + val jsonString = format.encodeToString(data) + println(jsonString) + // {"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} + + // Deserializes the JSON back to Response + val deserializedData = format.decodeFromString>(jsonString) + println(deserializedData) + // OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) } diff --git a/guide/example/example-poly-16.kt b/guide/example/example-poly-16.kt index 079dc70cec..ae9d0a3c31 100644 --- a/guide/example/example-poly-16.kt +++ b/guide/example/example-poly-16.kt @@ -1,38 +1,51 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly16 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* -import kotlin.reflect.KClass -val module = SerializersModule { +@Serializable +abstract class Response + +@Serializable +@SerialName("OkResponse") +data class OkResponse(val data: T) : Response() + +val responseModule = SerializersModule { + polymorphic(Response::class) { + subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) + } +} + +val projectModule = SerializersModule { fun PolymorphicModuleBuilder.registerProjectSubclasses() { subclass(OwnedProject::class) } polymorphic(Any::class) { registerProjectSubclasses() } polymorphic(Project::class) { registerProjectSubclasses() } -} - -val format = Json { serializersModule = module } - -interface Project { - val name: String } @Serializable -@SerialName("owned") -class OwnedProject(override val name: String, val owner: String) : Project +abstract class Project { + abstract val name: String +} @Serializable -class Data( - val project: Project, - @Polymorphic val any: Any -) +@SerialName("OwnedProject") +data class OwnedProject(override val name: String, val owner: String) : Project() + +// Now classes from both hierarchies can be serialized together and deserialized together. +val format = Json { serializersModule = projectModule + responseModule } +// The JSON that is being produced is deeply polymorphic. fun main() { - val project = OwnedProject("kotlinx.coroutines", "kotlin") - val data = Data(project, project) - println(format.encodeToString(data)) -} + // both Response and Project are abstract and their concrete subtypes are being serialized + val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) + val string = format.encodeToString(data) + println(string) + // {"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} + println(format.decodeFromString>(string)) + // OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) +} diff --git a/guide/example/example-poly-17.kt b/guide/example/example-poly-17.kt index 0b15a15934..cf48616220 100644 --- a/guide/example/example-poly-17.kt +++ b/guide/example/example-poly-17.kt @@ -1,48 +1,41 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly17 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - import kotlinx.serialization.modules.* -@Serializable -abstract class Response - -@Serializable -@SerialName("OkResponse") -data class OkResponse(val data: T) : Response() - -val responseModule = SerializersModule { - polymorphic(Response::class) { - subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) - } -} - -val projectModule = SerializersModule { - fun PolymorphicModuleBuilder.registerProjectSubclasses() { - subclass(OwnedProject::class) - } - polymorphic(Any::class) { registerProjectSubclasses() } - polymorphic(Project::class) { registerProjectSubclasses() } -} - @Serializable abstract class Project { abstract val name: String } - + +// Represents unknown project types, capturing the type and name +@Serializable +data class BasicProject(override val name: String, val type: String): Project() + @Serializable @SerialName("OwnedProject") data class OwnedProject(override val name: String, val owner: String) : Project() -val format = Json { serializersModule = projectModule + responseModule } +// Registers a default deserializer for unknown Project subtypes +val module = SerializersModule { + polymorphic(Project::class) { + subclass(OwnedProject::class) + defaultDeserializer { BasicProject.serializer() } + } +} + +val format = Json { serializersModule = module } fun main() { - // both Response and Project are abstract and their concrete subtypes are being serialized - val data: Response = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) - val string = format.encodeToString(data) - println(string) - println(format.decodeFromString>(string)) + // Deserializes both a known and an unknown Project subtype + println(format.decodeFromString>(""" + [ + {"type":"unknown","name":"example"}, + {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} + ] + """)) + // [BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)] } - diff --git a/guide/example/example-poly-18.kt b/guide/example/example-poly-18.kt index 18ac3ec64a..dd9a859758 100644 --- a/guide/example/example-poly-18.kt +++ b/guide/example/example-poly-18.kt @@ -1,30 +1,80 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.examplePoly18 +// Imports the necessary libraries import kotlinx.serialization.* import kotlinx.serialization.json.* - +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import kotlinx.serialization.modules.* -@Serializable -abstract class Project { - abstract val name: String +// sampleStart +interface Animal { +} + +interface Cat : Animal { + val catType: String +} + +interface Dog : Animal { + val dogType: String } -@Serializable -@SerialName("OwnedProject") -data class OwnedProject(override val name: String, val owner: String) : Project() +private class CatImpl : Cat { + override val catType: String = "Tabby" +} + +private class DogImpl : Dog { + override val dogType: String = "Husky" +} + +// Provides instances of Cat and Dog +object AnimalProvider { + fun createCat(): Cat = CatImpl() + fun createDog(): Dog = DogImpl() +} +// Registers a default serializer for unknown Animal subtypes val module = SerializersModule { - polymorphic(Project::class) { - subclass(OwnedProject::class) + polymorphicDefaultSerializer(Animal::class) { instance -> + @Suppress("UNCHECKED_CAST") + when (instance) { + is Cat -> CatSerializer as SerializationStrategy + is Dog -> DogSerializer as SerializationStrategy + else -> null + } } } +// Defines custom serializers for Cat and Dog +object CatSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Cat") { + element("catType") + } + + override fun serialize(encoder: Encoder, value: Cat) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.catType) + } + } +} + +object DogSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Dog") { + element("dogType") + } + + override fun serialize(encoder: Encoder, value: Dog) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.dogType) + } + } +} +// Creates a Json format using the registered module val format = Json { serializersModule = module } fun main() { - println(format.decodeFromString(""" - {"type":"unknown","name":"example"} - """)) + // Serializes an instance of Cat + println(format.encodeToString(AnimalProvider.createCat())) + // {"type":"Cat","catType":"Tabby"} } diff --git a/guide/example/example-poly-19.kt b/guide/example/example-poly-19.kt deleted file mode 100644 index 83677ebe47..0000000000 --- a/guide/example/example-poly-19.kt +++ /dev/null @@ -1,37 +0,0 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. -package example.examplePoly19 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -import kotlinx.serialization.modules.* - -@Serializable -abstract class Project { - abstract val name: String -} - -@Serializable -data class BasicProject(override val name: String, val type: String): Project() - -@Serializable -@SerialName("OwnedProject") -data class OwnedProject(override val name: String, val owner: String) : Project() - -val module = SerializersModule { - polymorphic(Project::class) { - subclass(OwnedProject::class) - defaultDeserializer { BasicProject.serializer() } - } -} - -val format = Json { serializersModule = module } - -fun main() { - println(format.decodeFromString>(""" - [ - {"type":"unknown","name":"example"}, - {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} - ] - """)) -} diff --git a/guide/example/example-poly-20.kt b/guide/example/example-poly-20.kt deleted file mode 100644 index b597fbeb3d..0000000000 --- a/guide/example/example-poly-20.kt +++ /dev/null @@ -1,74 +0,0 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. -package example.examplePoly20 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* - -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* - -interface Animal { -} - -interface Cat : Animal { - val catType: String -} - -interface Dog : Animal { - val dogType: String -} - -private class CatImpl : Cat { - override val catType: String = "Tabby" -} - -private class DogImpl : Dog { - override val dogType: String = "Husky" -} - -object AnimalProvider { - fun createCat(): Cat = CatImpl() - fun createDog(): Dog = DogImpl() -} - -val module = SerializersModule { - polymorphicDefaultSerializer(Animal::class) { instance -> - @Suppress("UNCHECKED_CAST") - when (instance) { - is Cat -> CatSerializer as SerializationStrategy - is Dog -> DogSerializer as SerializationStrategy - else -> null - } - } -} - -object CatSerializer : SerializationStrategy { - override val descriptor = buildClassSerialDescriptor("Cat") { - element("catType") - } - - override fun serialize(encoder: Encoder, value: Cat) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.catType) - } - } -} - -object DogSerializer : SerializationStrategy { - override val descriptor = buildClassSerialDescriptor("Dog") { - element("dogType") - } - - override fun serialize(encoder: Encoder, value: Dog) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.dogType) - } - } -} - -val format = Json { serializersModule = module } - -fun main() { - println(format.encodeToString(AnimalProvider.createCat())) -} diff --git a/guide/example/example-serializer-01.kt b/guide/example/example-serializer-01.kt index 13aaa6a60b..a8e2c52f4c 100644 --- a/guide/example/example-serializer-01.kt +++ b/guide/example/example-serializer-01.kt @@ -1,13 +1,14 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer01 import kotlinx.serialization.* -import kotlinx.serialization.json.* +import kotlinx.serialization.descriptors.* @Serializable -class Color(val rgb: Int) +data class Color(val rgb: Int) fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} + val colorSerializer: KSerializer = Color.serializer() + println(colorSerializer.descriptor) + // Color(rgb: kotlin.Int) +} diff --git a/guide/example/example-serializer-02.kt b/guide/example/example-serializer-02.kt index 98bda6dcf6..80235a39d5 100644 --- a/guide/example/example-serializer-02.kt +++ b/guide/example/example-serializer-02.kt @@ -1,13 +1,47 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer02 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* -@Serializable -@SerialName("Color") -class Color(val rgb: Int) +// Binds the Color class with the custom ColorAsStringSerializer using the with property +@Serializable(with = ColorAsStringSerializer::class) +data class Color(val rgb: Int) + +// Creates the custom serializer for the Color class +object ColorAsStringSerializer : KSerializer { + // Defines the schema for the serialized data as a single string + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) + + // Defines how the Color object is converted to a string during serialization + override fun serialize(encoder: Encoder, value: Color) { + // Converts the RGB value to a hex string + val hexValue = value.rgb.toString(16).padStart(6, '0') + // Encodes the hex string using the encodeString() function + encoder.encodeString(hexValue) + } + + // Defines how the string is converted back to a Color object during deserialization + override fun deserialize(decoder: Decoder): Color { + // Decodes the string using the decodeString() function + val hexValue = decoder.decodeString() + // Converts the hex value back into a Color object + return Color(hexValue.toInt(16)) + } +} fun main() { - val colorSerializer: KSerializer = Color.serializer() - println(colorSerializer.descriptor) -} + val color = Color(0x00FF00) + // Serializes a color to JSON + val jsonString = Json.encodeToString(color) + println(jsonString) + // "00ff00" + + // Deserializes the color back + val deserializedColor = Json.decodeFromString(jsonString) + println(deserializedColor.rgb) + // 65280 +} diff --git a/guide/example/example-serializer-03.kt b/guide/example/example-serializer-03.kt index cbf8772e84..b3a8a82043 100644 --- a/guide/example/example-serializer-03.kt +++ b/guide/example/example-serializer-03.kt @@ -1,17 +1,40 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer03 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.builtins.IntArraySerializer +import kotlinx.serialization.json.* -@Serializable -@SerialName("Color") -class Color(val rgb: Int) +// Creates a custom serializer that delegates to IntArraySerializer +class ColorIntArraySerializer : KSerializer { + private val delegateSerializer = IntArraySerializer() + override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor) + + // Delegates serialization logic to IntArraySerializer + override fun serialize(encoder: Encoder, value: Color) { + val data = intArrayOf( + (value.rgb shr 16) and 0xFF, + (value.rgb shr 8) and 0xFF, + value.rgb and 0xFF + ) + encoder.encodeSerializableValue(delegateSerializer, data) + } -@Serializable -@SerialName("Box") -class Box(val contents: T) + // Delegates deserialization logic to IntArraySerializer + override fun deserialize(decoder: Decoder): Color { + val array = decoder.decodeSerializableValue(delegateSerializer) + return Color((array[0] shl 16) or (array[1] shl 8) or array[2]) + } +} + +@Serializable(with = ColorIntArraySerializer::class) +class Color(val rgb: Int) fun main() { - val boxedColorSerializer = Box.serializer(Color.serializer()) - println(boxedColorSerializer.descriptor) -} + val green = Color(0x00ff00) + println(Json.encodeToString(green)) + // [0,255,0] +} diff --git a/guide/example/example-serializer-04.kt b/guide/example/example-serializer-04.kt index 455cc1eb0d..67cfe1b653 100644 --- a/guide/example/example-serializer-04.kt +++ b/guide/example/example-serializer-04.kt @@ -1,10 +1,46 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer04 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.builtins.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.builtins.IntArraySerializer +import kotlinx.serialization.json.* + +// Defines a private surrogate class with custom properties +@Serializable +@SerialName("Color") +private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { + init { + // Ensures values are within a valid range + require(r in 0..255 && g in 0..255 && b in 0..255) + } +} + +// Custom serializer that delegates to the surrogate class +object ColorSerializer : KSerializer { + override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor + + // Serializes the original class as a surrogate + override fun serialize(encoder: Encoder, value: Color) { + val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) + encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) + } + + // Deserializes the surrogate back into the original class + override fun deserialize(decoder: Decoder): Color { + val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) + return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) + } +} + +// Binds the ColorSerializer serializer to the original class +@Serializable(with = ColorSerializer::class) +class Color(val rgb: Int) fun main() { - val intSerializer: KSerializer = Int.serializer() - println(intSerializer.descriptor) + val green = Color(0x00ff00) + println(Json.encodeToString(green)) + // {"r":0,"g":255,"b":0} } diff --git a/guide/example/example-serializer-05.kt b/guide/example/example-serializer-05.kt index b9583c8989..e9597f9606 100644 --- a/guide/example/example-serializer-05.kt +++ b/guide/example/example-serializer-05.kt @@ -1,10 +1,64 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer05 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.builtins.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* -fun main() { - val stringListSerializer: KSerializer> = ListSerializer(String.serializer()) - println(stringListSerializer.descriptor) +// Creates a custom serializer for the Color class with multiple properties +object ColorAsObjectSerializer : KSerializer { + // Defines the schema for the Color class + // specifying the properties with the buildClassSerialDescriptor() function + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Color") { + // Specifies each property with its type and name with the element() function + element("r") + element("g") + element("b") + } + + // Serializes the Color object into a structured format + override fun serialize(encoder: Encoder, value: Color) = + encoder.encodeStructure(descriptor) { + // Encodes the red, green, and blue values in the specified order + encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) + encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) + encodeIntElement(descriptor, 2, value.rgb and 0xff) + } + + // Deserializes the data back into a Color object + override fun deserialize(decoder: Decoder): Color = + decoder.decodeStructure(descriptor) { + // Temporary variables to hold the decoded values + var r = -1 + var g = -1 + var b = -1 + // Loops to decode each property by its index + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> r = decodeIntElement(descriptor, 0) + 1 -> g = decodeIntElement(descriptor, 1) + 2 -> b = decodeIntElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + // Ensures the values are valid and returns a new Color object + require(r in 0..255 && g in 0..255 && b in 0..255) + Color((r shl 16) or (g shl 8) or b) + } +} + +// Binds the custom serializer to the Color class +@Serializable(with = ColorAsObjectSerializer::class) +data class Color(val rgb: Int) + +fun main() { + val color = Color(0x00ff00) + val string = Json.encodeToString(color) + println(string) + // {"r":0,"g":255,"b":0} + require(Json.decodeFromString(string) == color) } diff --git a/guide/example/example-serializer-06.kt b/guide/example/example-serializer-06.kt index 0c8be2e1cc..ac1dd24bc8 100644 --- a/guide/example/example-serializer-06.kt +++ b/guide/example/example-serializer-06.kt @@ -1,13 +1,61 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer06 +// Imports the necessary libraries import kotlinx.serialization.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* -@Serializable -@SerialName("Color") -class Color(val rgb: Int) -fun main() { - val stringToColorMapSerializer: KSerializer> = serializer() - println(stringToColorMapSerializer.descriptor) +object ColorAsObjectSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Color") { + element("r") + element("g") + element("b") + } + + override fun serialize(encoder: Encoder, value: Color) = + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) + encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) + encodeIntElement(descriptor, 2, value.rgb and 0xff) + } + + override fun deserialize(decoder: Decoder): Color = + decoder.decodeStructure(descriptor) { + var r = -1 + var g = -1 + var b = -1 + // Decodes values directly in order if the format stores data sequentially + if (decodeSequentially()) { + r = decodeIntElement(descriptor, 0) + g = decodeIntElement(descriptor, 1) + b = decodeIntElement(descriptor, 2) + } else while (true) { + // Decodes elements using a loop to handle formats where data may be unordered + when (val index = decodeElementIndex(descriptor)) { + 0 -> r = decodeIntElement(descriptor, 0) + 1 -> g = decodeIntElement(descriptor, 1) + 2 -> b = decodeIntElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + require(r in 0..255 && g in 0..255 && b in 0..255) + Color((r shl 16) or (g shl 8) or b) + } +} + +@Serializable(with = ColorAsObjectSerializer::class) +data class Color(val rgb: Int) + +fun main() { + val color = Color(0x00ff00) + val string = Json.encodeToString(color) + println(string) + // {"r":0,"g":255,"b":0} + require(Json.decodeFromString(string) == color) } diff --git a/guide/example/example-serializer-07.kt b/guide/example/example-serializer-07.kt index 3ebafa21fb..e594d276cc 100644 --- a/guide/example/example-serializer-07.kt +++ b/guide/example/example-serializer-07.kt @@ -1,29 +1,33 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer07 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* -object ColorAsStringSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: Color) { - val string = value.rgb.toString(16).padStart(6, '0') - encoder.encodeString(string) - } +// Marks the Box class with @Serializable and specifies a custom serializer +@Serializable(with = BoxSerializer::class) +data class Box(val contents: T) - override fun deserialize(decoder: Decoder): Color { - val string = decoder.decodeString() - return Color(string.toInt(16)) - } +// Creates a custom serializer as a class for Box +class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer> { + // Uses the descriptor from the provided KSerializer to define the structure of Box + override val descriptor: SerialDescriptor = dataSerializer.descriptor + // Delegates serialization and deserialization + override fun serialize(encoder: Encoder, value: Box) = dataSerializer.serialize(encoder, value.contents) + override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) } -@Serializable(with = ColorAsStringSerializer::class) -class Color(val rgb: Int) +@Serializable +data class Project(val name: String) fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} + val box = Box(Project("kotlinx.serialization")) + val string = Json.encodeToString(box) + println(string) + // {"name":"kotlinx.serialization"} + println(Json.decodeFromString>(string)) + // Box(contents=Project(name=kotlinx.serialization)) +} diff --git a/guide/example/example-serializer-08.kt b/guide/example/example-serializer-08.kt index 73c8c8100f..731b948d68 100644 --- a/guide/example/example-serializer-08.kt +++ b/guide/example/example-serializer-08.kt @@ -1,29 +1,39 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.exampleSerializer08 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* +import kotlinx.serialization.json.* +import java.util.Date +import java.text.SimpleDateFormat -object ColorAsStringSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) +// Defines a custom serializer for Date +object DateAsLongSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} - override fun serialize(encoder: Encoder, value: Color) { - val string = value.rgb.toString(16).padStart(6, '0') - encoder.encodeString(string) - } +@Serializable +class ProgrammingLanguage( + val name: String, + // Applies @Contextual to dynamically serialize the Date property + @Contextual + val stableReleaseDate: Date +) - override fun deserialize(decoder: Decoder): Color { - val string = decoder.decodeString() - return Color(string.toInt(16)) - } +// Defines the SerializersModule and registers DateAsLongSerializer using the contextual() function +private val module = SerializersModule { + contextual(DateAsLongSerializer) } -@Serializable(with = ColorAsStringSerializer::class) -class Color(val rgb: Int) +// Creates an instance of Json with the custom SerializersModule +val format = Json { serializersModule = module } fun main() { - val color = Json.decodeFromString("\"00ff00\"") - println(color.rgb) // prints 65280 -} + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(format.encodeToString(data)) + // {"name":"Kotlin","stableReleaseDate":1455494400000} +} diff --git a/guide/example/example-serializer-09.kt b/guide/example/example-serializer-09.kt deleted file mode 100644 index 4fe711195e..0000000000 --- a/guide/example/example-serializer-09.kt +++ /dev/null @@ -1,34 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer09 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -object ColorAsStringSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: Color) { - val string = value.rgb.toString(16).padStart(6, '0') - encoder.encodeString(string) - } - - override fun deserialize(decoder: Decoder): Color { - val string = decoder.decodeString() - return Color(string.toInt(16)) - } -} - -@Serializable(with = ColorAsStringSerializer::class) -data class Color(val rgb: Int) - -@Serializable -data class Settings(val background: Color, val foreground: Color) - -fun main() { - val data = Settings(Color(0xffffff), Color(0)) - val string = Json.encodeToString(data) - println(string) - require(Json.decodeFromString(string) == data) -} diff --git a/guide/example/example-serializer-10.kt b/guide/example/example-serializer-10.kt deleted file mode 100644 index 2b756524d3..0000000000 --- a/guide/example/example-serializer-10.kt +++ /dev/null @@ -1,36 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer10 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -import kotlinx.serialization.builtins.IntArraySerializer - -class ColorIntArraySerializer : KSerializer { - private val delegateSerializer = IntArraySerializer() - override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor) - - override fun serialize(encoder: Encoder, value: Color) { - val data = intArrayOf( - (value.rgb shr 16) and 0xFF, - (value.rgb shr 8) and 0xFF, - value.rgb and 0xFF - ) - encoder.encodeSerializableValue(delegateSerializer, data) - } - - override fun deserialize(decoder: Decoder): Color { - val array = decoder.decodeSerializableValue(delegateSerializer) - return Color((array[0] shl 16) or (array[1] shl 8) or array[2]) - } -} - -@Serializable(with = ColorIntArraySerializer::class) -class Color(val rgb: Int) - -fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} diff --git a/guide/example/example-serializer-11.kt b/guide/example/example-serializer-11.kt deleted file mode 100644 index 3931aa0236..0000000000 --- a/guide/example/example-serializer-11.kt +++ /dev/null @@ -1,36 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer11 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -@Serializable -@SerialName("Color") -private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { - init { - require(r in 0..255 && g in 0..255 && b in 0..255) - } -} - -object ColorSerializer : KSerializer { - override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor - - override fun serialize(encoder: Encoder, value: Color) { - val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) - encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) - } - - override fun deserialize(decoder: Decoder): Color { - val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) - return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) - } -} - -@Serializable(with = ColorSerializer::class) -class Color(val rgb: Int) -fun main() { - val green = Color(0x00ff00) - println(Json.encodeToString(green)) -} diff --git a/guide/example/example-serializer-12.kt b/guide/example/example-serializer-12.kt deleted file mode 100644 index e53c703237..0000000000 --- a/guide/example/example-serializer-12.kt +++ /dev/null @@ -1,52 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer12 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -object ColorAsObjectSerializer : KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Color") { - element("r") - element("g") - element("b") - } - - override fun serialize(encoder: Encoder, value: Color) = - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) - encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) - encodeIntElement(descriptor, 2, value.rgb and 0xff) - } - - override fun deserialize(decoder: Decoder): Color = - decoder.decodeStructure(descriptor) { - var r = -1 - var g = -1 - var b = -1 - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> r = decodeIntElement(descriptor, 0) - 1 -> g = decodeIntElement(descriptor, 1) - 2 -> b = decodeIntElement(descriptor, 2) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - require(r in 0..255 && g in 0..255 && b in 0..255) - Color((r shl 16) or (g shl 8) or b) - } -} - -@Serializable(with = ColorAsObjectSerializer::class) -data class Color(val rgb: Int) - -fun main() { - val color = Color(0x00ff00) - val string = Json.encodeToString(color) - println(string) - require(Json.decodeFromString(string) == color) -} diff --git a/guide/example/example-serializer-13.kt b/guide/example/example-serializer-13.kt deleted file mode 100644 index f2b0888283..0000000000 --- a/guide/example/example-serializer-13.kt +++ /dev/null @@ -1,56 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer13 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -object ColorAsObjectSerializer : KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Color") { - element("r") - element("g") - element("b") - } - - override fun serialize(encoder: Encoder, value: Color) = - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) - encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) - encodeIntElement(descriptor, 2, value.rgb and 0xff) - } - - override fun deserialize(decoder: Decoder): Color = - decoder.decodeStructure(descriptor) { - var r = -1 - var g = -1 - var b = -1 - if (decodeSequentially()) { // sequential decoding protocol - r = decodeIntElement(descriptor, 0) - g = decodeIntElement(descriptor, 1) - b = decodeIntElement(descriptor, 2) - } else while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> r = decodeIntElement(descriptor, 0) - 1 -> g = decodeIntElement(descriptor, 1) - 2 -> b = decodeIntElement(descriptor, 2) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - require(r in 0..255 && g in 0..255 && b in 0..255) - Color((r shl 16) or (g shl 8) or b) - } -} - -@Serializable(with = ColorAsObjectSerializer::class) -data class Color(val rgb: Int) - -fun main() { - val color = Color(0x00ff00) - val string = Json.encodeToString(color) - println(string) - require(Json.decodeFromString(string) == color) -} diff --git a/guide/example/example-serializer-19.kt b/guide/example/example-serializer-19.kt deleted file mode 100644 index 4622665a08..0000000000 --- a/guide/example/example-serializer-19.kt +++ /dev/null @@ -1,26 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer19 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -@Serializable(with = BoxSerializer::class) -data class Box(val contents: T) - -class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer> { - override val descriptor: SerialDescriptor = dataSerializer.descriptor - override fun serialize(encoder: Encoder, value: Box) = dataSerializer.serialize(encoder, value.contents) - override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) -} - -@Serializable -data class Project(val name: String) - -fun main() { - val box = Box(Project("kotlinx.serialization")) - val string = Json.encodeToString(box) - println(string) - println(Json.decodeFromString>(string)) -} diff --git a/guide/example/example-serializer-20.kt b/guide/example/example-serializer-20.kt deleted file mode 100644 index 38b72e794a..0000000000 --- a/guide/example/example-serializer-20.kt +++ /dev/null @@ -1,22 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer20 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -import java.util.Date -import java.text.SimpleDateFormat - -@Serializable -class ProgrammingLanguage( - val name: String, - @Contextual - val stableReleaseDate: Date -) - -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(Json.encodeToString(data)) -} diff --git a/guide/example/example-serializer-21.kt b/guide/example/example-serializer-21.kt deleted file mode 100644 index 9a24b0aaa7..0000000000 --- a/guide/example/example-serializer-21.kt +++ /dev/null @@ -1,35 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer21 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -import kotlinx.serialization.modules.* -import java.util.Date -import java.text.SimpleDateFormat - -object DateAsLongSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) - override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) - override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) -} - -@Serializable -class ProgrammingLanguage( - val name: String, - @Contextual - val stableReleaseDate: Date -) - -private val module = SerializersModule { - contextual(DateAsLongSerializer) -} - -val format = Json { serializersModule = module } - -fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(format.encodeToString(data)) -} diff --git a/guide/example/example-serializer-22.kt b/guide/example/example-serializer-22.kt deleted file mode 100644 index 4eba74b0be..0000000000 --- a/guide/example/example-serializer-22.kt +++ /dev/null @@ -1,18 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer22 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -// NOT @Serializable -class Project(val name: String, val language: String) - -@Serializer(forClass = Project::class) -object ProjectSerializer - -fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(ProjectSerializer, data)) -} diff --git a/guide/example/example-serializer-23.kt b/guide/example/example-serializer-23.kt deleted file mode 100644 index 4b7de25ae3..0000000000 --- a/guide/example/example-serializer-23.kt +++ /dev/null @@ -1,28 +0,0 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer23 - -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.descriptors.* - -// NOT @Serializable, will use external serializer -class Project( - // val in a primary constructor -- serialized - val name: String -) { - var stars: Int = 0 // property with getter & setter -- serialized - - val path: String // getter only -- not serialized - get() = "kotlin/$name" - - private var locked: Boolean = false // private, not accessible -- not serialized -} - -@Serializer(forClass = Project::class) -object ProjectSerializer - -fun main() { - val data = Project("kotlinx.serialization").apply { stars = 9000 } - println(Json.encodeToString(ProjectSerializer, data)) -} diff --git a/guide/example/example-serializer-14.kt b/guide/example/example-thirdparty-01.kt similarity index 70% rename from guide/example/example-serializer-14.kt rename to guide/example/example-thirdparty-01.kt index 684290d77e..dd3f67ee0c 100644 --- a/guide/example/example-serializer-14.kt +++ b/guide/example/example-thirdparty-01.kt @@ -1,14 +1,15 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer14 +// This file was automatically generated from third-party-classes.md by Knit tool. Do not edit. +package example.exampleThirdparty01 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* - +import kotlinx.serialization.json.* import java.util.Date import java.text.SimpleDateFormat +// Cannot use @Serializable on Date as without control over its source code object DateAsLongSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) @@ -17,5 +18,7 @@ object DateAsLongSerializer : KSerializer { fun main() { val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00") + // Serializes Date as a Long in milliseconds println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate)) + // 1455494400000 } diff --git a/guide/example/example-serializer-15.kt b/guide/example/example-thirdparty-02.kt similarity index 74% rename from guide/example/example-serializer-15.kt rename to guide/example/example-thirdparty-02.kt index a508846fa5..4e11a8cdb8 100644 --- a/guide/example/example-serializer-15.kt +++ b/guide/example/example-thirdparty-02.kt @@ -1,14 +1,14 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer15 +// This file was automatically generated from third-party-classes.md by Knit tool. Do not edit. +package example.exampleThirdparty02 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* - +import kotlinx.serialization.json.* import java.util.Date import java.text.SimpleDateFormat - + object DateAsLongSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) @@ -18,6 +18,7 @@ object DateAsLongSerializer : KSerializer { @Serializable class ProgrammingLanguage( val name: String, + // Specifies the custom serializer for the Date property @Serializable(with = DateAsLongSerializer::class) val stableReleaseDate: Date ) @@ -25,4 +26,5 @@ class ProgrammingLanguage( fun main() { val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) println(Json.encodeToString(data)) + // {"name":"Kotlin","stableReleaseDate":1455494400000} } diff --git a/guide/example/example-serializer-16.kt b/guide/example/example-thirdparty-03.kt similarity index 74% rename from guide/example/example-serializer-16.kt rename to guide/example/example-thirdparty-03.kt index 3db0b7ff11..4de5826db5 100644 --- a/guide/example/example-serializer-16.kt +++ b/guide/example/example-thirdparty-03.kt @@ -1,14 +1,14 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer16 +// This file was automatically generated from third-party-classes.md by Knit tool. Do not edit. +package example.exampleThirdparty03 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* - +import kotlinx.serialization.json.* import java.util.Date import java.text.SimpleDateFormat - + object DateAsLongSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) @@ -18,6 +18,7 @@ object DateAsLongSerializer : KSerializer { @Serializable class ProgrammingLanguage( val name: String, + // Applies the custom serializer to a List generic type val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> ) @@ -25,4 +26,5 @@ fun main() { val df = SimpleDateFormat("yyyy-MM-ddX") val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) println(Json.encodeToString(data)) + // {"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]} } diff --git a/guide/example/example-serializer-17.kt b/guide/example/example-thirdparty-04.kt similarity index 65% rename from guide/example/example-serializer-17.kt rename to guide/example/example-thirdparty-04.kt index c5624ed39b..1299a328e0 100644 --- a/guide/example/example-serializer-17.kt +++ b/guide/example/example-thirdparty-04.kt @@ -1,25 +1,29 @@ @file:UseSerializers(DateAsLongSerializer::class) -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer17 +// This file was automatically generated from third-party-classes.md by Knit tool. Do not edit. +package example.exampleThirdparty04 +// Specifies a serializer for the file +@file:UseSerializers(DateAsLongSerializer::class) +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* - +import kotlinx.serialization.json.* import java.util.Date import java.text.SimpleDateFormat - + object DateAsLongSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) } -@Serializable +// No need to specify the custom serializer on the property because it’s applied to the file +@Serializable class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) fun main() { val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) println(Json.encodeToString(data)) + // {"name":"Kotlin","stableReleaseDate":1455494400000} } diff --git a/guide/example/example-serializer-18.kt b/guide/example/example-thirdparty-05.kt similarity index 74% rename from guide/example/example-serializer-18.kt rename to guide/example/example-thirdparty-05.kt index b1ce1c9c82..f74987c282 100644 --- a/guide/example/example-serializer-18.kt +++ b/guide/example/example-thirdparty-05.kt @@ -1,36 +1,37 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. -package example.exampleSerializer18 +// This file was automatically generated from third-party-classes.md by Knit tool. Do not edit. +package example.exampleThirdparty05 +// Imports the necessary libraries import kotlinx.serialization.* -import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* - +import kotlinx.serialization.json.* import java.util.Date -import java.util.TimeZone import java.text.SimpleDateFormat - +import java.util.TimeZone + object DateAsLongSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsLong", PrimitiveKind.LONG) override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) } - +// Defines a serializer that encodes Date as a formatted string (yyyy-MM-dd) object DateAsSimpleTextSerializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG) private val format = SimpleDateFormat("yyyy-MM-dd").apply { - // Here we explicitly set time zone to UTC so output for this sample remains locale-independent. - // Depending on your needs, you may have to adjust or remove this line. + // Sets the time zone to UTC for consistent output setTimeZone(TimeZone.getTimeZone("UTC")) } override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value)) override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString()) } +// Applies global serializers using typealias to avoid annotating each occurrence typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date +// Uses typealiases to apply custom serializers for Date properties @Serializable class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) @@ -38,4 +39,5 @@ fun main() { val format = SimpleDateFormat("yyyy-MM-ddX") val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) println(Json.encodeToString(data)) + // {"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000} } diff --git a/guide/test/BuiltinClassesTest.kt b/guide/test/BuiltinClassesTest.kt index 3cc9629583..44ffd6f139 100644 --- a/guide/test/BuiltinClassesTest.kt +++ b/guide/test/BuiltinClassesTest.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-serialize-builtin-types.md by Knit tool. Do not edit. package example.test import org.junit.Test @@ -8,14 +8,14 @@ class BuiltinClassesTest { @Test fun testExampleBuiltin01() { captureOutput("ExampleBuiltin01") { example.exampleBuiltin01.main() }.verifyOutputLines( - "{\"answer\":42,\"pi\":3.141592653589793}" + "{\"signature\":2067120338512882656}" ) } @Test fun testExampleBuiltin02() { captureOutput("ExampleBuiltin02") { example.exampleBuiltin02.main() }.verifyOutputLines( - "{\"signature\":2067120338512882656}" + "{\"answer\":42,\"pi\":3.141592653589793}" ) } @@ -64,14 +64,14 @@ class BuiltinClassesTest { @Test fun testExampleBuiltin09() { captureOutput("ExampleBuiltin09") { example.exampleBuiltin09.main() }.verifyOutputLines( - "Data(a=[42, 42], b=[42])" + "{\"1\":{\"name\":\"kotlinx.serialization\"},\"2\":{\"name\":\"kotlinx.coroutines\"}}" ) } @Test fun testExampleBuiltin10() { captureOutput("ExampleBuiltin10") { example.exampleBuiltin10.main() }.verifyOutputLines( - "{\"1\":{\"name\":\"kotlinx.serialization\"},\"2\":{\"name\":\"kotlinx.coroutines\"}}" + "Data(a=[42, 42], b=[42])" ) } diff --git a/guide/test/FormatsTest.kt b/guide/test/FormatsTest.kt index f305a59bf2..d78c34afef 100644 --- a/guide/test/FormatsTest.kt +++ b/guide/test/FormatsTest.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from formats.md by Knit tool. Do not edit. +// This file was automatically generated from alternative-serialization-formats.md by Knit tool. Do not edit. package example.test import org.junit.Test @@ -29,39 +29,39 @@ class FormatsTest { } @Test - fun testExampleFormats04() { - captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "{0A}{15}kotlinx.serialization{12}{06}Kotlin", "Project(name=kotlinx.serialization, language=Kotlin)" ) } @Test - fun testExampleFormats05() { - captureOutput("ExampleFormats05") { example.exampleFormats05.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "{0A}{15}kotlinx.serialization{1A}{06}Kotlin", "Project(name=kotlinx.serialization, language=Kotlin)" ) } @Test - fun testExampleFormats06() { - captureOutput("ExampleFormats06") { example.exampleFormats06.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "{08}{01}{10}{03}{1D}{03}{00}{00}{00}" ) } @Test - fun testExampleFormats07() { - captureOutput("ExampleFormats07") { example.exampleFormats07.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "{08}{01}{08}{02}{08}{03}", "Data(a=[1, 2, 3], b=[])" ) } @Test - fun testExampleFormats08() { - captureOutput("ExampleFormats08") { example.exampleFormats08.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "0a03546f6d1203313233", "0a054a657272791a03373839", "Data(name=Tom, phone=HomePhone(number=123))", @@ -70,8 +70,8 @@ class FormatsTest { } @Test - fun testExampleFormats09() { - captureOutput("ExampleFormats09") { example.exampleFormats09.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "syntax = \"proto2\";", "", "", @@ -81,69 +81,76 @@ class FormatsTest { " optional string description = 2;", " // WARNING: a default value decoded when value is missing", " optional string department = 3;", - "}", - "" + "}" ) } @Test - fun testExampleFormats10() { - captureOutput("ExampleFormats10") { example.exampleFormats10.main() }.verifyOutputLines( + fun testExampleFormats03() { + captureOutput("ExampleFormats03") { example.exampleFormats03.main() }.verifyOutputLines( "name = kotlinx.serialization", "owner.name = kotlin" ) } @Test - fun testExampleFormats11() { - captureOutput("ExampleFormats11") { example.exampleFormats11.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( + "{\"base64Input\":\"Zm9vIHN0cmluZw==\"}", + "foo string" + ) + } + + @Test + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "[kotlinx.serialization, kotlin, 9000]" ) } @Test - fun testExampleFormats12() { - captureOutput("ExampleFormats12") { example.exampleFormats12.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "[kotlinx.serialization, kotlin, 9000]", "Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000)" ) } @Test - fun testExampleFormats13() { - captureOutput("ExampleFormats13") { example.exampleFormats13.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "[kotlinx.serialization, kotlin, 9000]", "Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=9000)" ) } @Test - fun testExampleFormats14() { - captureOutput("ExampleFormats14") { example.exampleFormats14.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "[kotlinx.serialization, 2, kotlin, jetbrains, 9000]", "Project(name=kotlinx.serialization, owners=[User(name=kotlin), User(name=jetbrains)], votes=9000)" ) } @Test - fun testExampleFormats15() { - captureOutput("ExampleFormats15") { example.exampleFormats15.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "[kotlinx.serialization, !!, kotlin, NULL]", "Project(name=kotlinx.serialization, owner=User(name=kotlin), votes=null)" ) } @Test - fun testExampleFormats16() { - captureOutput("ExampleFormats16") { example.exampleFormats16.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "{00}{15}kotlinx.serialization{00}{06}Kotlin", "Project(name=kotlinx.serialization, language=Kotlin)" ) } @Test - fun testExampleFormats17() { - captureOutput("ExampleFormats17") { example.exampleFormats17.main() }.verifyOutputLines( + fun testExampleFormats04() { + captureOutput("ExampleFormats04") { example.exampleFormats04.main() }.verifyOutputLines( "{00}{15}kotlinx.serialization{04}{0A}{0B}{0C}{0D}", "Project(name=kotlinx.serialization, attachment=[10, 11, 12, 13])" ) diff --git a/guide/test/JsonTest.kt b/guide/test/JsonTest.kt index 3cc0a7a780..be3c459af2 100644 --- a/guide/test/JsonTest.kt +++ b/guide/test/JsonTest.kt @@ -32,73 +32,73 @@ class JsonTest { @Test fun testExampleJson04() { captureOutput("ExampleJson04") { example.exampleJson04.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization)", - "Project(name=kotlinx.coroutines)" + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\",\"website\":null}" ) } @Test fun testExampleJson05() { captureOutput("ExampleJson05") { example.exampleJson05.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\",\"website\":null}" + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}", + "Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null)" ) } @Test fun testExampleJson06() { captureOutput("ExampleJson06") { example.exampleJson06.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}", - "Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null)" + "Project(name=kotlinx.serialization, language=Kotlin)" ) } @Test fun testExampleJson07() { captureOutput("ExampleJson07") { example.exampleJson07.main() }.verifyOutputLines( - "Project(name=kotlinx.serialization, language=Kotlin)" + "Brush(foreground=BLACK, background=null)" ) } @Test fun testExampleJson08() { captureOutput("ExampleJson08") { example.exampleJson08.main() }.verifyOutputLines( - "Brush(foreground=BLACK, background=null)" + "[{\"name\":\"kotlinx.serialization\"},\"Serialization\",{\"name\":\"kotlinx.coroutines\"},\"Coroutines\"]" ) } @Test fun testExampleJson09() { captureOutput("ExampleJson09") { example.exampleJson09.main() }.verifyOutputLines( - "[{\"name\":\"kotlinx.serialization\"},\"Serialization\",{\"name\":\"kotlinx.coroutines\"},\"Coroutines\"]" + "{\"value\":NaN}" ) } @Test fun testExampleJson10() { captureOutput("ExampleJson10") { example.exampleJson10.main() }.verifyOutputLines( - "{\"value\":NaN}" + "{\"#class\":\"simple\",\"name\":\"kotlinx.serialization\"}", + "{\"#class\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @Test fun testExampleJson11() { captureOutput("ExampleJson11") { example.exampleJson11.main() }.verifyOutputLines( - "{\"#class\":\"simple\",\"name\":\"kotlinx.serialization\"}", - "{\"#class\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + "{\"message\":{\"message_type\":\"my.app.BaseMessage\",\"message\":\"not found\"},\"error\":{\"message_type\":\"my.app.GenericError\",\"error_code\":404}}" ) } @Test fun testExampleJson12() { captureOutput("ExampleJson12") { example.exampleJson12.main() }.verifyOutputLines( - "{\"message\":{\"message_type\":\"my.app.BaseMessage\",\"message\":\"not found\"},\"error\":{\"message_type\":\"my.app.GenericError\",\"error_code\":404}}" + "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @Test fun testExampleJson13() { captureOutput("ExampleJson13") { example.exampleJson13.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + "Project(name=kotlinx.serialization)", + "Project(name=kotlinx.coroutines)" ) } diff --git a/guide/test/JsonTestElements.kt b/guide/test/JsonTestElements.kt index 46d2509c2c..9c3de14dcf 100644 --- a/guide/test/JsonTestElements.kt +++ b/guide/test/JsonTestElements.kt @@ -67,4 +67,11 @@ class JsonTestElements { "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive" ) } + + @Test + fun testExampleJsonElements09() { + captureOutput("ExampleJsonElements09") { example.exampleJsonElements09.main() }.verifyOutputLines( + "null" + ) + } } diff --git a/guide/test/PolymorphismTest.kt b/guide/test/PolymorphismTest.kt index 344ed24dab..14b63f17ce 100644 --- a/guide/test/PolymorphismTest.kt +++ b/guide/test/PolymorphismTest.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from serialization-polymorphism.md by Knit tool. Do not edit. package example.test import org.junit.Test @@ -15,31 +15,30 @@ class PolymorphismTest { @Test fun testExamplePoly02() { captureOutput("ExamplePoly02") { example.examplePoly02.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found.", - "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.", + "Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.", + "To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'." ) } @Test fun testExamplePoly03() { - captureOutput("ExamplePoly03") { example.examplePoly03.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.", - "Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.", - "To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'." + captureOutput("ExamplePoly03") { example.examplePoly03.main() }.verifyOutputLines( + "{\"type\":\"OwnedProject\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @Test fun testExamplePoly04() { captureOutput("ExamplePoly04") { example.examplePoly04.main() }.verifyOutputLines( - "{\"type\":\"example.examplePoly04.OwnedProject\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @Test fun testExamplePoly05() { captureOutput("ExamplePoly05") { example.examplePoly05.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + "{\"type\":\"OwnedProject\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @@ -60,7 +59,7 @@ class PolymorphismTest { @Test fun testExamplePoly08() { captureOutput("ExamplePoly08") { example.examplePoly08.main() }.verifyOutputLines( - "[{\"type\":\"example.examplePoly08.EmptyResponse\"},{\"type\":\"example.examplePoly08.TextResponse\",\"text\":\"OK\"}]" + "[{\"type\":\"EmptyResponse\"},{\"type\":\"TextResponse\",\"text\":\"OK\"}]" ) } @@ -87,67 +86,51 @@ class PolymorphismTest { @Test fun testExamplePoly12() { - captureOutput("ExamplePoly12") { example.examplePoly12.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.", - "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + captureOutput("ExamplePoly12") { example.examplePoly12.main() }.verifyOutputLines( + "{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" ) } @Test fun testExamplePoly13() { - captureOutput("ExamplePoly13") { example.examplePoly13.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.", - "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + captureOutput("ExamplePoly13") { example.examplePoly13.main() }.verifyOutputLines( + "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" ) } @Test fun testExamplePoly14() { captureOutput("ExamplePoly14") { example.examplePoly14.main() }.verifyOutputLines( - "{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}" + "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"},\"any\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" ) } @Test fun testExamplePoly15() { captureOutput("ExamplePoly15") { example.examplePoly15.main() }.verifyOutputLines( - "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" + "{\"type\":\"OkResponse\",\"data\":{\"type\":\"OwnedProject\",\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"}}", + "OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin))" ) } @Test fun testExamplePoly16() { captureOutput("ExamplePoly16") { example.examplePoly16.main() }.verifyOutputLines( - "{\"project\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"},\"any\":{\"type\":\"owned\",\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}}" - ) - } - - @Test - fun testExamplePoly17() { - captureOutput("ExamplePoly17") { example.examplePoly17.main() }.verifyOutputLines( "{\"type\":\"OkResponse\",\"data\":{\"type\":\"OwnedProject\",\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"}}", "OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin))" ) } @Test - fun testExamplePoly18() { - captureOutput("ExamplePoly18") { example.examplePoly18.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $", - "Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule." - ) - } - - @Test - fun testExamplePoly19() { - captureOutput("ExamplePoly19") { example.examplePoly19.main() }.verifyOutputLines( + fun testExamplePoly17() { + captureOutput("ExamplePoly17") { example.examplePoly17.main() }.verifyOutputLines( "[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)]" ) } @Test - fun testExamplePoly20() { - captureOutput("ExamplePoly20") { example.examplePoly20.main() }.verifyOutputLines( + fun testExamplePoly18() { + captureOutput("ExamplePoly18") { example.examplePoly18.main() }.verifyOutputLines( "{\"type\":\"Cat\",\"catType\":\"Tabby\"}" ) } diff --git a/guide/test/SerializersTest.kt b/guide/test/SerializersTest.kt index bda3f7f40d..b44fb8fb61 100644 --- a/guide/test/SerializersTest.kt +++ b/guide/test/SerializersTest.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from serializers.md by Knit tool. Do not edit. +// This file was automatically generated from create-custom-serializers.md by Knit tool. Do not edit. package example.test import org.junit.Test @@ -8,163 +8,58 @@ class SerializersTest { @Test fun testExampleSerializer01() { captureOutput("ExampleSerializer01") { example.exampleSerializer01.main() }.verifyOutputLines( - "{\"rgb\":65280}" + "Color(rgb: kotlin.Int)" ) } @Test fun testExampleSerializer02() { captureOutput("ExampleSerializer02") { example.exampleSerializer02.main() }.verifyOutputLines( - "Color(rgb: kotlin.Int)" + "\"00ff00\"", + "65280" ) } @Test fun testExampleSerializer03() { captureOutput("ExampleSerializer03") { example.exampleSerializer03.main() }.verifyOutputLines( - "Box(contents: Color)" + "[0,255,0]" ) } @Test fun testExampleSerializer04() { captureOutput("ExampleSerializer04") { example.exampleSerializer04.main() }.verifyOutputLines( - "PrimitiveDescriptor(kotlin.Int)" + "{\"r\":0,\"g\":255,\"b\":0}" ) } @Test fun testExampleSerializer05() { captureOutput("ExampleSerializer05") { example.exampleSerializer05.main() }.verifyOutputLines( - "kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))" + "{\"r\":0,\"g\":255,\"b\":0}" ) } @Test fun testExampleSerializer06() { captureOutput("ExampleSerializer06") { example.exampleSerializer06.main() }.verifyOutputLines( - "kotlin.collections.LinkedHashMap(PrimitiveDescriptor(kotlin.String), Color(rgb: kotlin.Int))" + "{\"r\":0,\"g\":255,\"b\":0}" ) } @Test fun testExampleSerializer07() { captureOutput("ExampleSerializer07") { example.exampleSerializer07.main() }.verifyOutputLines( - "\"00ff00\"" - ) - } - - @Test - fun testExampleSerializer08() { - captureOutput("ExampleSerializer08") { example.exampleSerializer08.main() }.verifyOutputLines( - "65280" - ) - } - - @Test - fun testExampleSerializer09() { - captureOutput("ExampleSerializer09") { example.exampleSerializer09.main() }.verifyOutputLines( - "{\"background\":\"ffffff\",\"foreground\":\"000000\"}" - ) - } - - @Test - fun testExampleSerializer10() { - captureOutput("ExampleSerializer10") { example.exampleSerializer10.main() }.verifyOutputLines( - "[0,255,0]" - ) - } - - @Test - fun testExampleSerializer11() { - captureOutput("ExampleSerializer11") { example.exampleSerializer11.main() }.verifyOutputLines( - "{\"r\":0,\"g\":255,\"b\":0}" - ) - } - - @Test - fun testExampleSerializer12() { - captureOutput("ExampleSerializer12") { example.exampleSerializer12.main() }.verifyOutputLines( - "{\"r\":0,\"g\":255,\"b\":0}" - ) - } - - @Test - fun testExampleSerializer13() { - captureOutput("ExampleSerializer13") { example.exampleSerializer13.main() }.verifyOutputLines( - "{\"r\":0,\"g\":255,\"b\":0}" - ) - } - - @Test - fun testExampleSerializer14() { - captureOutput("ExampleSerializer14") { example.exampleSerializer14.main() }.verifyOutputLines( - "1455494400000" - ) - } - - @Test - fun testExampleSerializer15() { - captureOutput("ExampleSerializer15") { example.exampleSerializer15.main() }.verifyOutputLines( - "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" - ) - } - - @Test - fun testExampleSerializer16() { - captureOutput("ExampleSerializer16") { example.exampleSerializer16.main() }.verifyOutputLines( - "{\"name\":\"Kotlin\",\"releaseDates\":[1688601600000,1682380800000,1672185600000]}" - ) - } - - @Test - fun testExampleSerializer17() { - captureOutput("ExampleSerializer17") { example.exampleSerializer17.main() }.verifyOutputLines( - "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" - ) - } - - @Test - fun testExampleSerializer18() { - captureOutput("ExampleSerializer18") { example.exampleSerializer18.main() }.verifyOutputLines( - "{\"stableReleaseDate\":\"2016-02-15\",\"lastReleaseTimestamp\":1657152000000}" - ) - } - - @Test - fun testExampleSerializer19() { - captureOutput("ExampleSerializer19") { example.exampleSerializer19.main() }.verifyOutputLines( "{\"name\":\"kotlinx.serialization\"}", "Box(contents=Project(name=kotlinx.serialization))" ) } @Test - fun testExampleSerializer20() { - captureOutput("ExampleSerializer20") { example.exampleSerializer20.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.", - "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." - ) - } - - @Test - fun testExampleSerializer21() { - captureOutput("ExampleSerializer21") { example.exampleSerializer21.main() }.verifyOutputLines( + fun testExampleSerializer08() { + captureOutput("ExampleSerializer08") { example.exampleSerializer08.main() }.verifyOutputLines( "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" ) } - - @Test - fun testExampleSerializer22() { - captureOutput("ExampleSerializer22") { example.exampleSerializer22.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" - ) - } - - @Test - fun testExampleSerializer23() { - captureOutput("ExampleSerializer23") { example.exampleSerializer23.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"stars\":9000}" - ) - } } diff --git a/guide/test/SerializersThirdParty.kt b/guide/test/SerializersThirdParty.kt new file mode 100644 index 0000000000..7ceda55d9e --- /dev/null +++ b/guide/test/SerializersThirdParty.kt @@ -0,0 +1,56 @@ +// This file was automatically generated from third-party-classes.md by Knit tool. Do not edit. +package example.test + +import org.junit.Test +import kotlinx.knit.test.* + +class SerializersThirdParty { + @Test + fun testExampleThirdparty01() { + captureOutput("ExampleThirdparty01") { example.exampleThirdparty01.main() }.verifyOutputLines( + "1455494400000" + ) + } + + @Test + fun testExampleThirdparty02() { + captureOutput("ExampleThirdparty02") { example.exampleThirdparty02.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + ) + } + + @Test + fun testExampleThirdparty03() { + captureOutput("ExampleThirdparty03") { example.exampleThirdparty03.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"releaseDates\":[1688601600000,1682380800000,1672185600000]}" + ) + } + + @Test + fun testExampleThirdparty04() { + captureOutput("ExampleThirdparty04") { example.exampleThirdparty04.main() }.verifyOutputLines( + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + ) + } + + @Test + fun testExampleThirdparty05() { + captureOutput("ExampleThirdparty05") { example.exampleThirdparty05.main() }.verifyOutputLines( + "{\"stableReleaseDate\":\"2016-02-15\",\"lastReleaseTimestamp\":1657152000000}" + ) + } + + @Test + fun testExampleThirdparty05() { + captureOutput("ExampleThirdparty05") { example.exampleThirdparty05.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + ) + } + + @Test + fun testExampleThirdparty05() { + captureOutput("ExampleThirdparty05") { example.exampleThirdparty05.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"stars\":9000}" + ) + } +} From 9404b2dcb44a8c28f00c127a3347f9850c572dcb Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Thu, 17 Oct 2024 13:35:38 +0200 Subject: [PATCH 13/14] removed question marks from whats next titles --- docs/topics/configure-json-serialization.md | 2 +- docs/topics/serialization-get-started.md | 2 +- docs/topics/serialization-json-configuration.md | 2 +- docs/topics/serialization-json-elements.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/configure-json-serialization.md b/docs/topics/configure-json-serialization.md index d573aa3327..0c74995e27 100644 --- a/docs/topics/configure-json-serialization.md +++ b/docs/topics/configure-json-serialization.md @@ -57,7 +57,7 @@ val customJson = Json { } ``` -## What's next? +## What's next * Learn how to [customize JSON serialization settings](serialization-json-configuration.md) to address different use cases. * Explore [advanced JSON element handling](serialization-json-elements.md) to manipulate and work with JSON data before it is parsed or serialized. diff --git a/docs/topics/serialization-get-started.md b/docs/topics/serialization-get-started.md index c6f6828d6b..fe07d17f14 100644 --- a/docs/topics/serialization-get-started.md +++ b/docs/topics/serialization-get-started.md @@ -240,7 +240,7 @@ To deserialize an object from JSON in Kotlin: Congratulations! You have successfully serialized an object to JSON and deserialized it back into an object in Kotlin! -## What's next? +## What's next * Learn how to serialize standard types, including built-in types like numbers and strings, in [Serialize built-in types](serialization-serialize-builtin-types.md). * Discover how to customize class serialization and adjust the default behavior of the `@Serializable` annotation in the [Serialize classes](serialization-customization-options.md) section. diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md index 71dda0e257..7977fa2f69 100644 --- a/docs/topics/serialization-json-configuration.md +++ b/docs/topics/serialization-json-configuration.md @@ -779,7 +779,7 @@ Given these factors, it’s important to weigh the pros and cons before implemen -## What's next? +## What's next * Explore [advanced JSON element handling](serialization-json-elements.md) to manipulate and work with JSON data before it is parsed or serialized. * Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. diff --git a/docs/topics/serialization-json-elements.md b/docs/topics/serialization-json-elements.md index a5f341d71f..ee6ef85a9a 100644 --- a/docs/topics/serialization-json-elements.md +++ b/docs/topics/serialization-json-elements.md @@ -418,7 +418,7 @@ null -## What's next? +## What's next * Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. * Learn how to [serialize classes](serialization-customization-options.md) and how to modify the default behavior of the `@Serializable` annotation. From 4eb867cef67b0974ea9406130323a2ac2976a2d8 Mon Sep 17 00:00:00 2001 From: Daniel Csorba Date: Wed, 6 Nov 2024 13:40:56 +0100 Subject: [PATCH 14/14] fixing staging related issues --- docs/topics/alternative-serialization-formats.md | 8 ++++---- docs/topics/serialization-json-configuration.md | 2 +- docs/topics/serialization-polymorphism.md | 2 +- docs/topics/serialization-serialize-builtin-types.md | 2 +- docs/topics/serialization-transform-json.md | 2 +- docs/topics/third-party-classes.md | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/topics/alternative-serialization-formats.md b/docs/topics/alternative-serialization-formats.md index 1babab2265..636103267a 100644 --- a/docs/topics/alternative-serialization-formats.md +++ b/docs/topics/alternative-serialization-formats.md @@ -363,7 +363,7 @@ Project(name=kotlinx.serialization, language=Kotlin) In [ProtoBuf hex notation](https://protogen.marcgravell.com/decode), the output is equivalent to the following: -```protobuf +```groovy Field #1: 0A String Length = 21, Hex = 15, UTF8 = "kotlinx.serialization" Field #2: 12 String Length = 6, Hex = 06, UTF8 = "Kotlin" ``` @@ -567,7 +567,7 @@ This feature enables a field to hold one of several possible types, but only one Consider this ProtoBuf message definition: -```protobuf +```groovy message Data { required string name = 1; oneof phone { @@ -656,7 +656,7 @@ You can also define a class without the `@ProtoOneOf` annotation if you plan to For example: -```protobuf +```groovy @Serializable data class Data2( @ProtoNumber(1) val name: String, @@ -1420,7 +1420,7 @@ providing flexibility to optimize performance and compatibility with other syste In the following example, a custom encoder and decoder are implemented using [`java.io.DataOutput`](https://docs.oracle.com/javase/8/docs/api/java/io/DataOutput.html) and [`java.io.DataInput`](https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html) interfaces. -This approach enables detailed control over the serialization of each [primitive type](serialization.md#supported-formats). +This approach enables detailed control over the serialization of each [primitive type](create-custom-serializers.md#create-a-custom-primitive-serializer). First, override the encode functions for each primitive type, such as `encodeInt()` for integers or `encodeString()` for strings. This allows you to write the binary data directly to the `DataOutput` stream: diff --git a/docs/topics/serialization-json-configuration.md b/docs/topics/serialization-json-configuration.md index 7977fa2f69..c713273cdc 100644 --- a/docs/topics/serialization-json-configuration.md +++ b/docs/topics/serialization-json-configuration.md @@ -297,7 +297,7 @@ The current supported invalid values are: {style="note"} If value is missing, it is replaced with a default property value if it exists. -For enums, if no default is defined and the [`explicitNulls`]((#omit-explicit-nulls)) property is set to `false`, +For enums, if no default is defined and the [`explicitNulls`](#omit-explicit-nulls) property is set to `false`, the value is replaced with `null` if the property is nullable: ```kotlin diff --git a/docs/topics/serialization-polymorphism.md b/docs/topics/serialization-polymorphism.md index c701d7b14e..e4cd584728 100644 --- a/docs/topics/serialization-polymorphism.md +++ b/docs/topics/serialization-polymorphism.md @@ -941,7 +941,7 @@ fun main() { Using a default serializer assumes that the structure of the "unknown" data is known in advance. In cases where the structure may vary, you need to create a custom serializer to handle more flexible or less-structured data. -For more details on working with custom JSON structures, see the [Maintain custom JSON attributes](serialization-json-transform-json.md#maintain-custom-json-attributes) section. +For more details on working with custom JSON structures, see the [Maintain custom JSON attributes](serialization-transform-json.md#maintain-custom-json-attributes) section. diff --git a/docs/topics/serialization-serialize-builtin-types.md b/docs/topics/serialization-serialize-builtin-types.md index 0dc9d847ad..b1685c5e8a 100644 --- a/docs/topics/serialization-serialize-builtin-types.md +++ b/docs/topics/serialization-serialize-builtin-types.md @@ -415,7 +415,7 @@ Data(a=[42, 42], b=[42]) ### Unit and singleton objects The Kotlin [`Unit`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/) type, along with other singleton objects, is serializable. -A [singleton]((object-declarations.md)) is a class with only one instance, meaning its state is defined by the object itself, not by external properties. +A [singleton](object-declarations.md) is a class with only one instance, meaning its state is defined by the object itself, not by external properties. In JSON, singleton objects are serialized as empty structures: ```kotlin diff --git a/docs/topics/serialization-transform-json.md b/docs/topics/serialization-transform-json.md index d29508a61b..53e761d172 100644 --- a/docs/topics/serialization-transform-json.md +++ b/docs/topics/serialization-transform-json.md @@ -189,7 +189,7 @@ fun main() { > When serializing an object directly, you need to explicitly pass the custom serializer to the `encodeToString()` -> function to ensure that the custom serialization logic is applied. For more information, see the [Pass serializers manually](third-party-classes.html#pass-serializers-manually) section. +> function to ensure that the custom serialization logic is applied. For more information, see the [Pass serializers manually](third-party-classes.md#pass-serializers-manually) section. > {style="note"} diff --git a/docs/topics/third-party-classes.md b/docs/topics/third-party-classes.md index d2b296238b..2a4c9c4320 100644 --- a/docs/topics/third-party-classes.md +++ b/docs/topics/third-party-classes.md @@ -206,7 +206,7 @@ There is no global serializer configuration, except for [contextual serializatio However, in larger projects, repeatedly specifying the same serializers, like `Date` across many files can become tedious when used across many files. -In such cases, you can use [`typealias`](type-alias.md) to apply custom serializers globally for specific types, +In such cases, you can use [`typealias`](type-aliases.md) to apply custom serializers globally for specific types, eliminating the need to annotate each occurrence: ```kotlin