summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonid Startsev <sandwwraith@users.noreply.github.com>2022-01-26 12:45:00 +0300
committerGitHub <noreply@github.com>2022-01-26 12:45:00 +0300
commit3ac9b89db9a71b2e14c1123572b86430bc3af466 (patch)
treebd5a78db7338dc5db1bd8fbc16b748f09a9a0e17
parentf3de87302fdd407f4e415e14f876ebab17d81d19 (diff)
downloadkotlinx.serialization-3ac9b89db9a71b2e14c1123572b86430bc3af466.tar.gz
Provide documentation for @EncodeDefault and @JsonClassDiscriminator (#1832)
Fixes #1657
-rw-r--r--docs/basic-serialization.md254
-rw-r--r--docs/json.md280
-rw-r--r--docs/serialization-guide.md4
-rw-r--r--guide/example/example-basic-02.kt2
-rw-r--r--guide/example/example-basic-03.kt2
-rw-r--r--guide/example/example-classes-01.kt6
-rw-r--r--guide/example/example-classes-02.kt8
-rw-r--r--guide/example/example-classes-04.kt2
-rw-r--r--guide/example/example-classes-05.kt2
-rw-r--r--guide/example/example-classes-06.kt4
-rw-r--r--guide/example/example-classes-07.kt2
-rw-r--r--guide/example/example-classes-08.kt2
-rw-r--r--guide/example/example-classes-09.kt2
-rw-r--r--guide/example/example-classes-10.kt18
-rw-r--r--guide/example/example-classes-11.kt10
-rw-r--r--guide/example/example-classes-12.kt12
-rw-r--r--guide/example/example-classes-13.kt4
-rw-r--r--guide/example/example-classes-14.kt13
-rw-r--r--guide/example/example-classes-15.kt13
-rw-r--r--guide/example/example-classes-16.kt13
-rw-r--r--guide/example/example-json-01.kt4
-rw-r--r--guide/example/example-json-02.kt10
-rw-r--r--guide/example/example-json-03.kt6
-rw-r--r--guide/example/example-json-05.kt2
-rw-r--r--guide/example/example-json-06.kt6
-rw-r--r--guide/example/example-json-08.kt6
-rw-r--r--guide/example/example-json-09.kt2
-rw-r--r--guide/example/example-json-10.kt4
-rw-r--r--guide/example/example-json-11.kt27
-rw-r--r--guide/example/example-json-12.kt10
-rw-r--r--guide/example/example-json-13.kt23
-rw-r--r--guide/example/example-json-14.kt18
-rw-r--r--guide/example/example-json-15.kt31
-rw-r--r--guide/example/example-json-16.kt22
-rw-r--r--guide/example/example-json-17.kt28
-rw-r--r--guide/example/example-json-18.kt34
-rw-r--r--guide/example/example-json-19.kt59
-rw-r--r--guide/example/example-json-20.kt58
-rw-r--r--guide/example/example-json-21.kt37
-rw-r--r--guide/test/BasicSerializationTest.kt24
-rw-r--r--guide/test/JsonTest.kt33
41 files changed, 634 insertions, 463 deletions
diff --git a/docs/basic-serialization.md b/docs/basic-serialization.md
index 14a3e82d..f3c5d916 100644
--- a/docs/basic-serialization.md
+++ b/docs/basic-serialization.md
@@ -20,7 +20,7 @@ This chapter shows the basic use of Kotlin Serialization and explains its core c
* [Optional property initializer call](#optional-property-initializer-call)
* [Required properties](#required-properties)
* [Transient properties](#transient-properties)
- * [Defaults are not encoded](#defaults-are-not-encoded)
+ * [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)
@@ -33,21 +33,21 @@ This chapter shows the basic use of Kotlin Serialization and explains its core c
## 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_&mdash;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_&mdash;it is the conversion of the corresponding sequence of primitives into
+through two mutually intertwined processes. In the first step, an object is _serialized_&mdash;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_&mdash;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.
+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.
@@ -58,72 +58,72 @@ import kotlinx.serialization.json.*
### 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 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
+```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
+```text
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.
Mark the class as @Serializable or provide the serializer explicitly.
```
<!--- TEST LINES_START -->
-Serializable classes have to be explicitly marked. Kotlin Serialization does not use reflection,
+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
+@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
+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"}
```
-
-<!--- TEST -->
+
+<!--- TEST -->
> 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.
+> 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.
+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.
+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
+@Serializable
data class Project(val name: String, val language: String)
fun main() {
@@ -132,7 +132,7 @@ fun main() {
""")
println(data)
}
-```
+```
> You can get the full code [here](../guide/example/example-basic-03.kt).
@@ -141,12 +141,12 @@ Running this code we get back the object.
```text
Project(name=kotlinx.serialization, language=Kotlin)
```
-
-<!--- TEST -->
+
+<!--- TEST -->
## Serializable classes
-This section goes into more details on how different `@Serializable` classes are handled.
+This section goes into more details on how different `@Serializable` classes are handled.
<!--- INCLUDE .*-classes-.*
import kotlinx.serialization.*
@@ -158,16 +158,16 @@ import kotlinx.serialization.json.*
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
+```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"
+ get() = "kotlin/$name"
var id by ::name // delegated property -- not serialized
}
@@ -176,13 +176,13 @@ 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
+```text
{"name":"kotlinx.serialization","stars":9000}
```
@@ -190,14 +190,14 @@ We can clearly see that only the `name` and `stars` properties are present in th
### Constructor properties requirement
-If we want to define the `Project` class so that it takes a path string, and then
+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
+@Serializable
class Project(path: String) {
- val owner: String = path.substringBefore('/')
- val name: String = path.substringAfter('/')
+ val owner: String = path.substringBefore('/')
+ val name: String = path.substringAfter('/')
}
```
@@ -208,17 +208,17 @@ constructor be properties. A simple workaround is to define a private primary co
properties, and turn the constructor we wanted into the secondary one.
```kotlin
-@Serializable
+@Serializable
class Project private constructor(val owner: String, val name: String) {
constructor(path: String) : this(
- owner = path.substringBefore('/'),
+ owner = path.substringBefore('/'),
name = path.substringAfter('/')
- )
+ )
val path: String
- get() = "$owner/$name"
+ get() = "$owner/$name"
}
-```
+```
Serialization works with a private primary constructor, and still serializes only backing fields.
@@ -226,13 +226,13 @@ Serialization works with a private primary constructor, and still serializes onl
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
+```text
{"owner":"kotlin","name":"kotlinx.serialization"}
```
@@ -241,7 +241,7 @@ This example produces the expected output.
### 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
+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
@@ -253,7 +253,7 @@ class Project(val name: String) {
}
```
-A deserialization process works like a regular constructor in Kotlin and calls all `init` blocks, ensuring that you
+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
@@ -263,13 +263,13 @@ fun main() {
""")
println(data)
}
-```
+```
> You can get the full code [here](../guide/example/example-classes-03.kt).
Running this code produces the exception:
-```text
+```text
Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty
```
@@ -281,7 +281,7 @@ An object can be deserialized only when all its properties are present in the in
For example, run the following code.
```kotlin
-@Serializable
+@Serializable
data class Project(val name: String, val language: String)
fun main() {
@@ -290,7 +290,7 @@ fun main() {
""")
println(data)
}
-```
+```
> You can get the full code [here](../guide/example/example-classes-04.kt).
@@ -298,7 +298,7 @@ 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
-```
+```
<!--- TEST LINES_START -->
@@ -306,7 +306,7 @@ This problem can be fixed by adding a default value to the property, which autom
for serialization.
```kotlin
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
@@ -315,7 +315,7 @@ fun main() {
""")
println(data)
}
-```
+```
> You can get the full code [here](../guide/example/example-classes-05.kt).
@@ -323,7 +323,7 @@ It produces the following output with the default value for the `language` prope
```text
Project(name=kotlinx.serialization, language=Kotlin)
-```
+```
<!--- TEST -->
@@ -339,26 +339,26 @@ fun computeLanguage(): String {
return "Kotlin"
}
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = computeLanguage())
-
+
fun main() {
val data = Json.decodeFromString<Project>("""
{"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)
-```
-
+```
+
<!--- TEST -->
### Required properties
@@ -367,7 +367,7 @@ A property with a default value can be required in a serial format with the [`@R
Let us change the previous example by marking the `language` property as `@Required`.
```kotlin
-@Serializable
+@Serializable
data class Project(val name: String, @Required val language: String = "Kotlin")
fun main() {
@@ -376,7 +376,7 @@ fun main() {
""")
println(data)
}
-```
+```
> You can get the full code [here](../guide/example/example-classes-07.kt).
@@ -384,17 +384,17 @@ 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
-```
+```
<!--- TEST LINES_START -->
### Transient properties
-A property can be excluded from serialization by marking it with the [`@Transient`][Transient] annotation
+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
+@Serializable
data class Project(val name: String, @Transient val language: String = "Kotlin")
fun main() {
@@ -403,7 +403,7 @@ fun main() {
""")
println(data)
}
-```
+```
> You can get the full code [here](../guide/example/example-classes-08.kt).
@@ -413,26 +413,26 @@ 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'.
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
-```
+```
<!--- TEST LINES_START -->
> The 'ignoreUnknownKeys' feature is explained in the [Ignoring Unknown Keys section](json.md#ignoring-unknown-keys) section.
-### Defaults are not encoded
+### 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.
+such configuration reduces visual clutter, and saves the amount of data being serialized.
```kotlin
-@Serializable
+@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).
@@ -440,9 +440,49 @@ It produces the following output, which does not have the `language` property be
```text
{"name":"kotlinx.serialization"}
-```
+```
-> See [Encoding defaults](json.md#encoding-defaults) section on how this behavior can be configured for JSON.
+<!--- TEST -->
+
+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<Project> = 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"}
+```
<!--- TEST -->
@@ -458,25 +498,25 @@ fun main() {
val data = Project("kotlinx.serialization")
println(Json.encodeToString(data))
}
-```
+```
-> You can get the full code [here](../guide/example/example-classes-10.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).
```text
{"name":"kotlinx.serialization"}
-```
+```
<!--- TEST -->
### Type safety is enforced
-Kotlin Serialization strongly enforces the type safety of the Kotlin programming language.
+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
+@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
@@ -485,26 +525,26 @@ fun main() {
""")
println(data)
}
-```
+```
-> You can get the full code [here](../guide/example/example-classes-11.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
+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.
Use 'coerceInputValues = true' in 'Json {}` builder to coerce nulls to default values.
-```
+```
<!--- TEST LINES_START -->
> 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.
+> 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.
+Serializable classes can reference other classes in their serializable properties.
The referenced classes must be also marked as `@Serializable`.
```kotlin
@@ -519,9 +559,9 @@ fun main() {
val data = Project("kotlinx.serialization", owner)
println(Json.encodeToString(data))
}
-```
+```
-> You can get the full code [here](../guide/example/example-classes-12.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.
@@ -530,7 +570,7 @@ When encoded to JSON it results in a nested JSON object.
```
> 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.
+> custom serializer can be provided for them as shown in the [Serializers](serializers.md) chapter.
<!--- TEST -->
@@ -552,32 +592,32 @@ fun main() {
val data = Project("kotlinx.serialization", owner, owner)
println(Json.encodeToString(data))
}
-```
+```
-> You can get the full code [here](../guide/example/example-classes-13.kt).
+> 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.
+> 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.
<!--- TEST -->
### Generic classes
-Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at
+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<T>`.
```kotlin
@Serializable
class Box<T>(val contents: T)
-```
+```
-The `Box<T>` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`.
+The `Box<T>` class can be used with builtin types like `Int`, as well as with user-defined types like `Project`.
<!--- INCLUDE
@@ -596,13 +636,13 @@ 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-14.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`.
-```text
+```text
{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}}
```
@@ -612,9 +652,9 @@ If the actual generic type is not serializable a compile-time error will be prod
### Serial field names
-The names of the properties used in encoded representation, JSON in our examples, are the same as
+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
+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
@@ -625,15 +665,15 @@ fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(data))
}
-```
+```
-> You can get the full code [here](../guide/example/example-classes-15.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.
```text
{"name":"kotlinx.serialization","lang":"Kotlin"}
-```
+```
<!--- TEST -->
@@ -652,6 +692,8 @@ The next chapter covers [Builtin classes](builtin-classes.md).
[kotlinx.serialization.decodeFromString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html
[Required]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/index.html
[Transient]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html
+[EncodeDefault]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/index.html
+[EncodeDefault.Mode]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/index.html
[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
<!--- MODULE /kotlinx-serialization-json -->
diff --git a/docs/json.md b/docs/json.md
index 1be2b506..606d4aca 100644
--- a/docs/json.md
+++ b/docs/json.md
@@ -19,7 +19,7 @@ In this chapter, we'll walk through features of [JSON](https://www.json.org/json
* [Explicit nulls](#explicit-nulls)
* [Allowing structured map keys](#allowing-structured-map-keys)
* [Allowing special floating-point values](#allowing-special-floating-point-values)
- * [Class discriminator](#class-discriminator)
+ * [Class discriminator for polymorphism](#class-discriminator-for-polymorphism)
* [Json elements](#json-elements)
* [Parsing to Json element](#parsing-to-json-element)
* [Types of Json elements](#types-of-json-elements)
@@ -39,15 +39,15 @@ In this chapter, we'll walk through features of [JSON](https://www.json.org/json
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_.
+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
+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.
+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.
+> may cache format-specific additional information about the classes they serialize.
This chapter shows configuration features that [Json] supports.
@@ -64,10 +64,10 @@ and line breaks for better readability) by setting the [prettyPrint][JsonBuilder
```kotlin
val format = Json { prettyPrint = true }
-@Serializable
+@Serializable
data class Project(val name: String, val language: String)
-fun main() {
+fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(format.encodeToString(data))
}
@@ -88,21 +88,21 @@ It gives the following nice result:
### Lenient parsing
-By default, [Json] parser enforces various JSON restrictions to be as specification-compliant as possible
+By default, [Json] parser enforces various JSON restrictions to be as specification-compliant as possible
(see [RFC-4627]). Particularly, keys must be quoted, while literals must be unquoted. 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 }
+enum class Status { SUPPORTED }
-@Serializable
+@Serializable
data class Project(val name: String, val status: Status, val votes: Int)
-
-fun main() {
+
+fun main() {
val data = format.decodeFromString<Project>("""
- {
+ {
name : kotlinx.serialization,
status : SUPPORTED,
votes : "9000"
@@ -115,7 +115,7 @@ fun main() {
> 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:
+integer is quoted:
```text
Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000)
@@ -133,10 +133,10 @@ to `true`:
```kotlin
val format = Json { ignoreUnknownKeys = true }
-@Serializable
+@Serializable
data class Project(val name: String)
-
-fun main() {
+
+fun main() {
val data = format.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
@@ -147,10 +147,10 @@ fun main() {
> 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)
-```
+```
<!--- TEST -->
@@ -158,7 +158,7 @@ Project(name=kotlinx.serialization)
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.
+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
@@ -182,8 +182,8 @@ 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
+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.
<!--- TEST -->
@@ -191,10 +191,10 @@ unless you want to do some fine-tuning.
### 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.
+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.
+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 and uses the default value of the corresponding property instead.
@@ -204,14 +204,14 @@ The current list of supported invalid values is:
* unknown values 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.
+> permissive to invalid value in the input, replacing them with defaults.
See the example from the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section:
```kotlin
val format = Json { coerceInputValues = true }
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
@@ -233,22 +233,22 @@ Project(name=kotlinx.serialization, language=Kotlin)
<!--- TEST -->
-### Encoding defaults
+### 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) section for details and an example.
-This is especially useful for nullable properties with null defaults and avoids writing the corresponding null values.
+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
+@Serializable
class Project(
- val name: String,
+ val name: String,
val language: String = "Kotlin",
val website: String? = null
-)
+)
fun main() {
val data = Project("kotlinx.serialization")
@@ -298,7 +298,7 @@ fun main() {
> You can get the full code [here](../guide/example/example-json-07.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,
+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
@@ -314,7 +314,7 @@ Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null
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
+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:
@@ -322,27 +322,27 @@ This is how you can serialize a map with keys of a user-defined class:
```kotlin
val format = Json { allowStructuredMapKeys = true }
-@Serializable
+@Serializable
data class Project(val name: String)
-
-fun main() {
+
+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-08.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"]
-```
+```
-<!--- TEST -->
+<!--- TEST -->
### Allowing special floating-point values
@@ -357,7 +357,7 @@ val format = Json { allowSpecialFloatingPointValues = true }
@Serializable
class Data(
val value: Double
-)
+)
fun main() {
val data = Data(Double.NaN)
@@ -376,9 +376,9 @@ special values in JVM world:
<!--- TEST -->
-### Class discriminator
+### Class discriminator for polymorphism
-A key name that specifies a type when you have a polymorphic data can be specified
+A key name that specifies a type when you have a polymorphic data can be specified
in the [classDiscriminator][JsonBuilder.classDiscriminator] property:
```kotlin
@@ -388,8 +388,8 @@ val format = Json { classDiscriminator = "#class" }
sealed class Project {
abstract val name: String
}
-
-@Serializable
+
+@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()
@@ -410,6 +410,62 @@ control over the resulting JSON object:
<!--- TEST -->
+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:
+
+<!--- INCLUDE
+
+@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()
+-->
+
+```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-11.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}}
+```
+
+<!--- TEST -->
+
+
## Json elements
Aside from direct conversions between strings and JSON objects, Kotlin serialization offers APIs that allow
@@ -422,7 +478,7 @@ The main concept in this part of the library is [JsonElement]. Read on to learn
### 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 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
@@ -434,7 +490,7 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-11.kt).
+> You can get the full code [here](../guide/example/example-json-12.kt).
A `JsonElement` prints itself as a valid JSON:
@@ -449,12 +505,12 @@ A `JsonElement` prints itself as a valid JSON:
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
+ 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:
@@ -477,11 +533,11 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-12.kt).
+> You can get the full code [here](../guide/example/example-json-13.kt).
The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`:
-```text
+```text
9042
```
@@ -517,10 +573,10 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-13.kt).
+> You can get the full code [here](../guide/example/example-json-14.kt).
As a result, you get a proper JSON string:
-
+
```text
{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]}
```
@@ -529,11 +585,11 @@ As a result, you get a proper JSON string:
### Decoding Json elements
-An instance of the [JsonElement] class can be decoded into a serializable object using
+An instance of the [JsonElement] class can be decoded into a serializable object using
the [Json.decodeFromJsonElement] function:
```kotlin
-@Serializable
+@Serializable
data class Project(val name: String, val language: String)
fun main() {
@@ -546,13 +602,13 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-14.kt).
+> You can get the full code [here](../guide/example/example-json-15.kt).
The result is exactly what you would expect:
-```text
+```text
Project(name=kotlinx.serialization, language=Kotlin)
-```
+```
<!--- TEST -->
@@ -561,15 +617,15 @@ Project(name=kotlinx.serialization, language=Kotlin)
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
+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
+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
+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
@@ -587,10 +643,10 @@ import kotlinx.serialization.builtins.*
-->
```kotlin
-@Serializable
+@Serializable
data class Project(
val name: String,
- @Serializable(with = UserListSerializer::class)
+ @Serializable(with = UserListSerializer::class)
val users: List<User>
)
@@ -598,8 +654,8 @@ data class Project(
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
+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
@@ -613,7 +669,7 @@ object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerialize
Now you can test the code with a JSON array or a single JSON object as inputs.
```kotlin
-fun main() {
+fun main() {
println(Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","users":{"name":"kotlin"}}
"""))
@@ -623,7 +679,7 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-15.kt).
+> You can get the full code [here](../guide/example/example-json-16.kt).
The output shows that both cases are correctly deserialized into a Kotlin [List].
@@ -636,16 +692,16 @@ Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrain
### 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:
<!--- INCLUDE
import kotlinx.serialization.builtins.*
-@Serializable
+@Serializable
data class Project(
val name: String,
- @Serializable(with = UserListSerializer::class)
+ @Serializable(with = UserListSerializer::class)
val users: List<User>
)
@@ -669,15 +725,15 @@ object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerialize
Now, if you serialize a single-element list of objects from Kotlin:
```kotlin
-fun main() {
+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-16.kt).
+> You can get the full code [here](../guide/example/example-json-17.kt).
-You end up with a single JSON object, not an array with one element:
+You end up with a single JSON object, not an array with one element:
```text
{"name":"kotlinx.serialization","users":{"name":"kotlin"}}
@@ -687,12 +743,12 @@ You end up with a single JSON object, not an array with one element:
### Manipulating default values
-Another kind of useful transformation is omitting specific values from the output JSON, for example, if it
+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
+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
@@ -720,7 +776,7 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-17.kt).
+> You can get the full code [here](../guide/example/example-json-18.kt).
See the effect of the custom serializer:
@@ -733,7 +789,7 @@ See the effect of the custom serializer:
### Content-based polymorphic deserialization
-Typically, [polymorphic serialization](polymorphism.md) requires a dedicated `"type"` key
+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.
@@ -742,31 +798,31 @@ the actual type by the shape of JSON, for example by the presence of a specific
[JsonContentPolymorphicSerializer] provides a skeleton implementation for such a strategy.
To use it, override its `selectDeserializer` method.
-Let's start with the following class hierarchy.
+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
+> 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.
-<!--- INCLUDE
+<!--- INCLUDE
import kotlinx.serialization.builtins.*
--->
+-->
```kotlin
@Serializable
abstract class Project {
abstract val name: String
-}
+}
+
+@Serializable
+data class BasicProject(override val name: String): Project()
-@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
+You can distinguish the `BasicProject` and `OwnedProject` subclasses by the presence of
the `owner` key in the JSON object.
```kotlin
@@ -793,37 +849,37 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-18.kt).
+> You can get the full code [here](../guide/example/example-json-19.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)]
-```
+```
<!--- TEST -->
### 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,
+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`
+* `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,
+* 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
+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
+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:
<!--- INCLUDE
@@ -871,7 +927,7 @@ class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSeria
}
```
-Having this serializable `Response` implementation, you can take any serializable payload for its data
+Having this serializable `Response` implementation, you can take any serializable payload for its data
and serialize or deserialize the corresponding responses:
```kotlin
@@ -889,7 +945,7 @@ fun main() {
}
```
-> You can get the full code [here](../guide/example/example-json-19.kt).
+> You can get the full code [here](../guide/example/example-json-20.kt).
This gives you fine-grained control on the representation of the `Response` class in the JSON output:
@@ -900,10 +956,10 @@ This gives you fine-grained control on the representation of the `Response` clas
<!--- TEST -->
-### 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.
+### 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` &ndash; a class with the `name` property and arbitrary details flattened into the same object:
@@ -916,7 +972,7 @@ import kotlinx.serialization.encoding.*
data class UnknownProject(val name: String, val details: JsonObject)
```
-However, the default plugin-generated serializer requires details
+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:
@@ -945,16 +1001,16 @@ object UnknownProjectSerializer : KSerializer<UnknownProject> {
}
}
```
-
+
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-20.kt).
+> You can get the full code [here](../guide/example/example-json-21.kt).
```text
UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"})
@@ -972,13 +1028,14 @@ The next chapter covers [Alternative and custom formats (experimental)](formats.
<!-- stdlib references -->
[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/
+[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/
+[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
+[InheritableSerialInfo]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-inheritable-serial-info/index.html
[KSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
[Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
@@ -1004,6 +1061,7 @@ The next chapter covers [Alternative and custom formats (experimental)](formats.
[JsonBuilder.allowStructuredMapKeys]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html
[JsonBuilder.allowSpecialFloatingPointValues]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html
[JsonBuilder.classDiscriminator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html
+[JsonClassDiscriminator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/index.html
[JsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/index.html
[Json.parseToJsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html
[JsonPrimitive]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/index.html
diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md
index ef65696e..9eda08ce 100644
--- a/docs/serialization-guide.md
+++ b/docs/serialization-guide.md
@@ -24,7 +24,7 @@ Once the project is set up, we can start serializing some classes.
* <a name='optional-property-initializer-call'></a>[Optional property initializer call](basic-serialization.md#optional-property-initializer-call)
* <a name='required-properties'></a>[Required properties](basic-serialization.md#required-properties)
* <a name='transient-properties'></a>[Transient properties](basic-serialization.md#transient-properties)
- * <a name='defaults-are-not-encoded'></a>[Defaults are not encoded](basic-serialization.md#defaults-are-not-encoded)
+ * <a name='defaults-are-not-encoded-by-default'></a>[Defaults are not encoded by default](basic-serialization.md#defaults-are-not-encoded-by-default)
* <a name='nullable-properties'></a>[Nullable properties](basic-serialization.md#nullable-properties)
* <a name='type-safety-is-enforced'></a>[Type safety is enforced](basic-serialization.md#type-safety-is-enforced)
* <a name='referenced-objects'></a>[Referenced objects](basic-serialization.md#referenced-objects)
@@ -115,7 +115,7 @@ Once the project is set up, we can start serializing some classes.
* <a name='explicit-nulls'></a>[Explicit nulls](json.md#explicit-nulls)
* <a name='allowing-structured-map-keys'></a>[Allowing structured map keys](json.md#allowing-structured-map-keys)
* <a name='allowing-special-floating-point-values'></a>[Allowing special floating-point values](json.md#allowing-special-floating-point-values)
- * <a name='class-discriminator'></a>[Class discriminator](json.md#class-discriminator)
+ * <a name='class-discriminator-for-polymorphism'></a>[Class discriminator for polymorphism](json.md#class-discriminator-for-polymorphism)
* <a name='json-elements'></a>[Json elements](json.md#json-elements)
* <a name='parsing-to-json-element'></a>[Parsing to Json element](json.md#parsing-to-json-element)
* <a name='types-of-json-elements'></a>[Types of Json elements](json.md#types-of-json-elements)
diff --git a/guide/example/example-basic-02.kt b/guide/example/example-basic-02.kt
index 28fff3e4..3a5ebb94 100644
--- a/guide/example/example-basic-02.kt
+++ b/guide/example/example-basic-02.kt
@@ -4,7 +4,7 @@ package example.exampleBasic02
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
class Project(val name: String, val language: String)
fun main() {
diff --git a/guide/example/example-basic-03.kt b/guide/example/example-basic-03.kt
index a0bb7b72..f70369e5 100644
--- a/guide/example/example-basic-03.kt
+++ b/guide/example/example-basic-03.kt
@@ -4,7 +4,7 @@ package example.exampleBasic03
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
data class Project(val name: String, val language: String)
fun main() {
diff --git a/guide/example/example-classes-01.kt b/guide/example/example-classes-01.kt
index 8ccc94b0..0330f655 100644
--- a/guide/example/example-classes-01.kt
+++ b/guide/example/example-classes-01.kt
@@ -4,15 +4,15 @@ package example.exampleClasses01
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@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"
+ get() = "kotlin/$name"
var id by ::name // delegated property -- not serialized
}
diff --git a/guide/example/example-classes-02.kt b/guide/example/example-classes-02.kt
index b7a92cca..969cba9e 100644
--- a/guide/example/example-classes-02.kt
+++ b/guide/example/example-classes-02.kt
@@ -4,15 +4,15 @@ package example.exampleClasses02
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
class Project private constructor(val owner: String, val name: String) {
constructor(path: String) : this(
- owner = path.substringBefore('/'),
+ owner = path.substringBefore('/'),
name = path.substringAfter('/')
- )
+ )
val path: String
- get() = "$owner/$name"
+ get() = "$owner/$name"
}
fun main() {
diff --git a/guide/example/example-classes-04.kt b/guide/example/example-classes-04.kt
index 8b1992f1..5b691111 100644
--- a/guide/example/example-classes-04.kt
+++ b/guide/example/example-classes-04.kt
@@ -4,7 +4,7 @@ package example.exampleClasses04
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
data class Project(val name: String, val language: String)
fun main() {
diff --git a/guide/example/example-classes-05.kt b/guide/example/example-classes-05.kt
index 14142feb..9143b261 100644
--- a/guide/example/example-classes-05.kt
+++ b/guide/example/example-classes-05.kt
@@ -4,7 +4,7 @@ package example.exampleClasses05
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
diff --git a/guide/example/example-classes-06.kt b/guide/example/example-classes-06.kt
index 8ea72ea2..c37b336d 100644
--- a/guide/example/example-classes-06.kt
+++ b/guide/example/example-classes-06.kt
@@ -9,9 +9,9 @@ fun computeLanguage(): String {
return "Kotlin"
}
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = computeLanguage())
-
+
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
diff --git a/guide/example/example-classes-07.kt b/guide/example/example-classes-07.kt
index 099dc129..19aa153b 100644
--- a/guide/example/example-classes-07.kt
+++ b/guide/example/example-classes-07.kt
@@ -4,7 +4,7 @@ package example.exampleClasses07
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
data class Project(val name: String, @Required val language: String = "Kotlin")
fun main() {
diff --git a/guide/example/example-classes-08.kt b/guide/example/example-classes-08.kt
index 17e439cd..d1cf638d 100644
--- a/guide/example/example-classes-08.kt
+++ b/guide/example/example-classes-08.kt
@@ -4,7 +4,7 @@ package example.exampleClasses08
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
data class Project(val name: String, @Transient val language: String = "Kotlin")
fun main() {
diff --git a/guide/example/example-classes-09.kt b/guide/example/example-classes-09.kt
index b7f43e94..79231f70 100644
--- a/guide/example/example-classes-09.kt
+++ b/guide/example/example-classes-09.kt
@@ -4,7 +4,7 @@ package example.exampleClasses09
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
diff --git a/guide/example/example-classes-10.kt b/guide/example/example-classes-10.kt
index deca0cf2..c5a1f739 100644
--- a/guide/example/example-classes-10.kt
+++ b/guide/example/example-classes-10.kt
@@ -5,9 +5,21 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
-class Project(val name: String, val renamedTo: String? = null)
+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<Project> = emptyList()
+)
fun main() {
- val data = Project("kotlinx.serialization")
- println(Json.encodeToString(data))
+ val userA = User("Alice", listOf(Project("kotlinx.serialization")))
+ val userB = User("Bob")
+ println(Json.encodeToString(userA))
+ println(Json.encodeToString(userB))
}
diff --git a/guide/example/example-classes-11.kt b/guide/example/example-classes-11.kt
index 6e63798f..18a921b9 100644
--- a/guide/example/example-classes-11.kt
+++ b/guide/example/example-classes-11.kt
@@ -4,12 +4,10 @@ package example.exampleClasses11
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
-data class Project(val name: String, val language: String = "Kotlin")
+@Serializable
+class Project(val name: String, val renamedTo: String? = null)
fun main() {
- val data = Json.decodeFromString<Project>("""
- {"name":"kotlinx.serialization","language":null}
- """)
- println(data)
+ val data = Project("kotlinx.serialization")
+ println(Json.encodeToString(data))
}
diff --git a/guide/example/example-classes-12.kt b/guide/example/example-classes-12.kt
index f88e3b6a..232ee475 100644
--- a/guide/example/example-classes-12.kt
+++ b/guide/example/example-classes-12.kt
@@ -5,13 +5,11 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
-class Project(val name: String, val owner: 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)
- println(Json.encodeToString(data))
+ val data = Json.decodeFromString<Project>("""
+ {"name":"kotlinx.serialization","language":null}
+ """)
+ println(data)
}
diff --git a/guide/example/example-classes-13.kt b/guide/example/example-classes-13.kt
index dc9794bb..4b93c0df 100644
--- a/guide/example/example-classes-13.kt
+++ b/guide/example/example-classes-13.kt
@@ -5,13 +5,13 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
-class Project(val name: String, val owner: User, val maintainer: User)
+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, owner)
+ val data = Project("kotlinx.serialization", owner)
println(Json.encodeToString(data))
}
diff --git a/guide/example/example-classes-14.kt b/guide/example/example-classes-14.kt
index 49c9fdc0..3f2d7ce0 100644
--- a/guide/example/example-classes-14.kt
+++ b/guide/example/example-classes-14.kt
@@ -5,18 +5,13 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
-class Box<T>(val contents: T)
+class Project(val name: String, val owner: User, val maintainer: User)
@Serializable
-data class Project(val name: String, val language: String)
-
-@Serializable
-class Data(
- val a: Box<Int>,
- val b: Box<Project>
-)
+class User(val name: String)
fun main() {
- val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
+ val owner = User("kotlin")
+ val data = Project("kotlinx.serialization", owner, owner)
println(Json.encodeToString(data))
}
diff --git a/guide/example/example-classes-15.kt b/guide/example/example-classes-15.kt
index 76eb1c85..b259e0d7 100644
--- a/guide/example/example-classes-15.kt
+++ b/guide/example/example-classes-15.kt
@@ -5,9 +5,18 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
-class Project(val name: String, @SerialName("lang") val language: String)
+class Box<T>(val contents: T)
+
+@Serializable
+data class Project(val name: String, val language: String)
+
+@Serializable
+class Data(
+ val a: Box<Int>,
+ val b: Box<Project>
+)
fun main() {
- val data = Project("kotlinx.serialization", "Kotlin")
+ val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
println(Json.encodeToString(data))
}
diff --git a/guide/example/example-classes-16.kt b/guide/example/example-classes-16.kt
new file mode 100644
index 00000000..4a8b1972
--- /dev/null
+++ b/guide/example/example-classes-16.kt
@@ -0,0 +1,13 @@
+// This file was automatically generated from basic-serialization.md by Knit tool. Do not edit.
+package example.exampleClasses16
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+@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/guide/example/example-json-01.kt b/guide/example/example-json-01.kt
index 99589038..d3fb2671 100644
--- a/guide/example/example-json-01.kt
+++ b/guide/example/example-json-01.kt
@@ -6,10 +6,10 @@ import kotlinx.serialization.json.*
val format = Json { prettyPrint = true }
-@Serializable
+@Serializable
data class Project(val name: String, val language: String)
-fun main() {
+fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(format.encodeToString(data))
}
diff --git a/guide/example/example-json-02.kt b/guide/example/example-json-02.kt
index 45ece0ca..6eda7624 100644
--- a/guide/example/example-json-02.kt
+++ b/guide/example/example-json-02.kt
@@ -6,14 +6,14 @@ import kotlinx.serialization.json.*
val format = Json { isLenient = true }
-enum class Status { SUPPORTED }
+enum class Status { SUPPORTED }
-@Serializable
+@Serializable
data class Project(val name: String, val status: Status, val votes: Int)
-
-fun main() {
+
+fun main() {
val data = format.decodeFromString<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 8afc8aef..77f0ae24 100644
--- a/guide/example/example-json-03.kt
+++ b/guide/example/example-json-03.kt
@@ -6,10 +6,10 @@ import kotlinx.serialization.json.*
val format = Json { ignoreUnknownKeys = true }
-@Serializable
+@Serializable
data class Project(val name: String)
-
-fun main() {
+
+fun main() {
val data = format.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
diff --git a/guide/example/example-json-05.kt b/guide/example/example-json-05.kt
index ec629cd9..e1b54225 100644
--- a/guide/example/example-json-05.kt
+++ b/guide/example/example-json-05.kt
@@ -6,7 +6,7 @@ import kotlinx.serialization.json.*
val format = Json { coerceInputValues = true }
-@Serializable
+@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
diff --git a/guide/example/example-json-06.kt b/guide/example/example-json-06.kt
index 17341c0e..605b4884 100644
--- a/guide/example/example-json-06.kt
+++ b/guide/example/example-json-06.kt
@@ -6,12 +6,12 @@ import kotlinx.serialization.json.*
val format = Json { encodeDefaults = true }
-@Serializable
+@Serializable
class Project(
- val name: String,
+ val name: String,
val language: String = "Kotlin",
val website: String? = null
-)
+)
fun main() {
val data = Project("kotlinx.serialization")
diff --git a/guide/example/example-json-08.kt b/guide/example/example-json-08.kt
index 046ff507..86e6298f 100644
--- a/guide/example/example-json-08.kt
+++ b/guide/example/example-json-08.kt
@@ -6,10 +6,10 @@ import kotlinx.serialization.json.*
val format = Json { allowStructuredMapKeys = true }
-@Serializable
+@Serializable
data class Project(val name: String)
-
-fun main() {
+
+fun main() {
val map = mapOf(
Project("kotlinx.serialization") to "Serialization",
Project("kotlinx.coroutines") to "Coroutines"
diff --git a/guide/example/example-json-09.kt b/guide/example/example-json-09.kt
index 00807fc9..1303fdd7 100644
--- a/guide/example/example-json-09.kt
+++ b/guide/example/example-json-09.kt
@@ -9,7 +9,7 @@ val format = Json { allowSpecialFloatingPointValues = true }
@Serializable
class Data(
val value: Double
-)
+)
fun main() {
val data = Data(Double.NaN)
diff --git a/guide/example/example-json-10.kt b/guide/example/example-json-10.kt
index e6b07c1a..49df395e 100644
--- a/guide/example/example-json-10.kt
+++ b/guide/example/example-json-10.kt
@@ -10,8 +10,8 @@ val format = Json { classDiscriminator = "#class" }
sealed class Project {
abstract val name: String
}
-
-@Serializable
+
+@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()
diff --git a/guide/example/example-json-11.kt b/guide/example/example-json-11.kt
index e37c5db2..57e350ad 100644
--- a/guide/example/example-json-11.kt
+++ b/guide/example/example-json-11.kt
@@ -4,9 +4,28 @@ package example.exampleJson11
import kotlinx.serialization.*
import kotlinx.serialization.json.*
+@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 element = Json.parseToJsonElement("""
- {"name":"kotlinx.serialization","language":"Kotlin"}
- """)
- println(element)
+ val data = Message(BaseMessage("not found"), GenericError(404))
+ println(format.encodeToString(data))
}
diff --git a/guide/example/example-json-12.kt b/guide/example/example-json-12.kt
index 925c3b50..cc98bf5c 100644
--- a/guide/example/example-json-12.kt
+++ b/guide/example/example-json-12.kt
@@ -6,13 +6,7 @@ import kotlinx.serialization.json.*
fun main() {
val element = Json.parseToJsonElement("""
- {
- "name": "kotlinx.serialization",
- "forks": [{"votes": 42}, {"votes": 9000}, {}]
- }
+ {"name":"kotlinx.serialization","language":"Kotlin"}
""")
- val sum = element
- .jsonObject["forks"]!!
- .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 }
- println(sum)
+ println(element)
}
diff --git a/guide/example/example-json-13.kt b/guide/example/example-json-13.kt
index 4115270e..97188ff5 100644
--- a/guide/example/example-json-13.kt
+++ b/guide/example/example-json-13.kt
@@ -5,19 +5,14 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
fun main() {
- val element = buildJsonObject {
- put("name", "kotlinx.serialization")
- putJsonObject("owner") {
- put("name", "kotlin")
+ val element = Json.parseToJsonElement("""
+ {
+ "name": "kotlinx.serialization",
+ "forks": [{"votes": 42}, {"votes": 9000}, {}]
}
- putJsonArray("forks") {
- addJsonObject {
- put("votes", 42)
- }
- addJsonObject {
- put("votes", 9000)
- }
- }
- }
- println(element)
+ """)
+ val sum = element
+ .jsonObject["forks"]!!
+ .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 }
+ println(sum)
}
diff --git a/guide/example/example-json-14.kt b/guide/example/example-json-14.kt
index 3b2d58bf..0e5ba362 100644
--- a/guide/example/example-json-14.kt
+++ b/guide/example/example-json-14.kt
@@ -4,14 +4,20 @@ package example.exampleJson14
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-@Serializable
-data class Project(val name: String, val language: String)
-
fun main() {
val element = buildJsonObject {
put("name", "kotlinx.serialization")
- put("language", "Kotlin")
+ putJsonObject("owner") {
+ put("name", "kotlin")
+ }
+ putJsonArray("forks") {
+ addJsonObject {
+ put("votes", 42)
+ }
+ addJsonObject {
+ put("votes", 9000)
+ }
+ }
}
- val data = Json.decodeFromJsonElement<Project>(element)
- println(data)
+ println(element)
}
diff --git a/guide/example/example-json-15.kt b/guide/example/example-json-15.kt
index 1fa9166d..0aa317f4 100644
--- a/guide/example/example-json-15.kt
+++ b/guide/example/example-json-15.kt
@@ -4,29 +4,14 @@ package example.exampleJson15
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.builtins.*
-
-@Serializable
-data class Project(
- val name: String,
- @Serializable(with = UserListSerializer::class)
- val users: List<User>
-)
-
@Serializable
-data class User(val name: String)
-
-object UserListSerializer : JsonTransformingSerializer<List<User>>(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
-}
+data class Project(val name: String, val language: String)
-fun main() {
- println(Json.decodeFromString<Project>("""
- {"name":"kotlinx.serialization","users":{"name":"kotlin"}}
- """))
- println(Json.decodeFromString<Project>("""
- {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]}
- """))
+fun main() {
+ val element = buildJsonObject {
+ put("name", "kotlinx.serialization")
+ put("language", "Kotlin")
+ }
+ val data = Json.decodeFromJsonElement<Project>(element)
+ println(data)
}
diff --git a/guide/example/example-json-16.kt b/guide/example/example-json-16.kt
index cb18b22c..b66d3ac2 100644
--- a/guide/example/example-json-16.kt
+++ b/guide/example/example-json-16.kt
@@ -6,10 +6,10 @@ import kotlinx.serialization.json.*
import kotlinx.serialization.builtins.*
-@Serializable
+@Serializable
data class Project(
val name: String,
- @Serializable(with = UserListSerializer::class)
+ @Serializable(with = UserListSerializer::class)
val users: List<User>
)
@@ -17,14 +17,16 @@ data class Project(
data class User(val name: String)
object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {
-
- override fun transformSerialize(element: JsonElement): JsonElement {
- require(element is JsonArray) // this serializer is used only with lists
- return element.singleOrNull() ?: element
- }
+ // 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
}
-fun main() {
- val data = Project("kotlinx.serialization", listOf(User("kotlin")))
- println(Json.encodeToString(data))
+fun main() {
+ println(Json.decodeFromString<Project>("""
+ {"name":"kotlinx.serialization","users":{"name":"kotlin"}}
+ """))
+ println(Json.decodeFromString<Project>("""
+ {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]}
+ """))
}
diff --git a/guide/example/example-json-17.kt b/guide/example/example-json-17.kt
index 07e5e938..7b1b88f3 100644
--- a/guide/example/example-json-17.kt
+++ b/guide/example/example-json-17.kt
@@ -4,19 +4,27 @@ package example.exampleJson17
import kotlinx.serialization.*
import kotlinx.serialization.json.*
+import kotlinx.serialization.builtins.*
+
+@Serializable
+data class Project(
+ val name: String,
+ @Serializable(with = UserListSerializer::class)
+ val users: List<User>
+)
+
@Serializable
-class Project(val name: String, val language: String)
+data class User(val name: String)
+
+object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {
-object ProjectSerializer : JsonTransformingSerializer<Project>(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"
- })
+ override fun transformSerialize(element: JsonElement): JsonElement {
+ require(element is JsonArray) // this serializer is used only with lists
+ return element.singleOrNull() ?: element
+ }
}
fun main() {
- val data = Project("kotlinx.serialization", "Kotlin")
- println(Json.encodeToString(data)) // using plugin-generated serializer
- println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer
+ val data = Project("kotlinx.serialization", listOf(User("kotlin")))
+ println(Json.encodeToString(data))
}
diff --git a/guide/example/example-json-18.kt b/guide/example/example-json-18.kt
index a646494f..d3da62d3 100644
--- a/guide/example/example-json-18.kt
+++ b/guide/example/example-json-18.kt
@@ -4,33 +4,19 @@ package example.exampleJson18
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.builtins.*
-
-@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()
+class Project(val name: String, val language: String)
-object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
- override fun selectDeserializer(element: JsonElement) = when {
- "owner" in element.jsonObject -> OwnedProject.serializer()
- else -> BasicProject.serializer()
- }
+object ProjectSerializer : JsonTransformingSerializer<Project>(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"
+ })
}
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))
+ val data = Project("kotlinx.serialization", "Kotlin")
+ println(Json.encodeToString(data)) // using plugin-generated serializer
+ println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer
}
diff --git a/guide/example/example-json-19.kt b/guide/example/example-json-19.kt
index 469cdd67..4455d637 100644
--- a/guide/example/example-json-19.kt
+++ b/guide/example/example-json-19.kt
@@ -4,56 +4,33 @@ package example.exampleJson19
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.encoding.*
+import kotlinx.serialization.builtins.*
-@Serializable(with = ResponseSerializer::class)
-sealed class Response<out T> {
- data class Ok<out T>(val data: T) : Response<T>()
- data class Error(val message: String) : Response<Nothing>()
+@Serializable
+abstract class Project {
+ abstract val name: String
}
-class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Response<T>> {
- override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) {
- element("Ok", buildClassSerialDescriptor("Ok") {
- element<String>("message")
- })
- element("Error", dataSerializer.descriptor)
- }
+@Serializable
+data class BasicProject(override val name: String): Project()
- override fun deserialize(decoder: Decoder): Response<T> {
- // 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<T>) {
- // 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)
+@Serializable
+data class OwnedProject(override val name: String, val owner: String) : Project()
+
+object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
+ override fun selectDeserializer(element: JsonElement) = when {
+ "owner" in element.jsonObject -> OwnedProject.serializer()
+ else -> BasicProject.serializer()
}
}
-@Serializable
-data class Project(val name: String)
-
fun main() {
- val responses = listOf(
- Response.Ok(Project("kotlinx.serialization")),
- Response.Error("Not found")
+ val data = listOf(
+ OwnedProject("kotlinx.serialization", "kotlin"),
+ BasicProject("example")
)
- val string = Json.encodeToString(responses)
+ val string = Json.encodeToString(ListSerializer(ProjectSerializer), data)
println(string)
- println(Json.decodeFromString<List<Response<Project>>>(string))
+ println(Json.decodeFromString(ListSerializer(ProjectSerializer), string))
}
diff --git a/guide/example/example-json-20.kt b/guide/example/example-json-20.kt
index d12547d7..e613a08f 100644
--- a/guide/example/example-json-20.kt
+++ b/guide/example/example-json-20.kt
@@ -7,31 +7,53 @@ import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
-data class UnknownProject(val name: String, val details: JsonObject)
+@Serializable(with = ResponseSerializer::class)
+sealed class Response<out T> {
+ data class Ok<out T>(val data: T) : Response<T>()
+ data class Error(val message: String) : Response<Nothing>()
+}
-object UnknownProjectSerializer : KSerializer<UnknownProject> {
- override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") {
- element<String>("name")
- element<JsonElement>("details")
+class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Response<T>> {
+ override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) {
+ element("Ok", buildClassSerialDescriptor("Ok") {
+ element<String>("message")
+ })
+ element("Error", dataSerializer.descriptor)
}
- 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 deserialize(decoder: Decoder): Response<T> {
+ // 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: UnknownProject) {
- error("Serialization is not supported")
+ override fun serialize(encoder: Encoder, value: Response<T>) {
+ // 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)
}
}
+@Serializable
+data class Project(val name: String)
+
fun main() {
- println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}"""))
+ val responses = listOf(
+ Response.Ok(Project("kotlinx.serialization")),
+ Response.Error("Not found")
+ )
+ val string = Json.encodeToString(responses)
+ println(string)
+ println(Json.decodeFromString<List<Response<Project>>>(string))
}
diff --git a/guide/example/example-json-21.kt b/guide/example/example-json-21.kt
new file mode 100644
index 00000000..92de429b
--- /dev/null
+++ b/guide/example/example-json-21.kt
@@ -0,0 +1,37 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson21
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+
+data class UnknownProject(val name: String, val details: JsonObject)
+
+object UnknownProjectSerializer : KSerializer<UnknownProject> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") {
+ element<String>("name")
+ element<JsonElement>("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")
+ }
+}
+
+fun main() {
+ println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}"""))
+}
diff --git a/guide/test/BasicSerializationTest.kt b/guide/test/BasicSerializationTest.kt
index 88c01857..d9552112 100644
--- a/guide/test/BasicSerializationTest.kt
+++ b/guide/test/BasicSerializationTest.kt
@@ -94,42 +94,50 @@ class BasicSerializationTest {
@Test
fun testExampleClasses10() {
captureOutput("ExampleClasses10") { example.exampleClasses10.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\"}"
+ "{\"name\":\"Alice\",\"projects\":[{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}]}",
+ "{\"name\":\"Bob\"}"
)
}
@Test
fun testExampleClasses11() {
- captureOutput("ExampleClasses11") { example.exampleClasses11.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.",
- "Use 'coerceInputValues = true' in 'Json {}` builder to coerce nulls to default values."
+ captureOutput("ExampleClasses11") { example.exampleClasses11.main() }.verifyOutputLines(
+ "{\"name\":\"kotlinx.serialization\"}"
)
}
@Test
fun testExampleClasses12() {
- captureOutput("ExampleClasses12") { example.exampleClasses12.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"}}"
+ 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.",
+ "Use 'coerceInputValues = true' in 'Json {}` builder to coerce nulls to default values."
)
}
@Test
fun testExampleClasses13() {
captureOutput("ExampleClasses13") { example.exampleClasses13.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"maintainer\":{\"name\":\"kotlin\"}}"
+ "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"}}"
)
}
@Test
fun testExampleClasses14() {
captureOutput("ExampleClasses14") { example.exampleClasses14.main() }.verifyOutputLines(
- "{\"a\":{\"contents\":42},\"b\":{\"contents\":{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}}}"
+ "{\"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/guide/test/JsonTest.kt b/guide/test/JsonTest.kt
index 3dee6e2b..c92f57bf 100644
--- a/guide/test/JsonTest.kt
+++ b/guide/test/JsonTest.kt
@@ -83,73 +83,80 @@ class JsonTest {
@Test
fun testExampleJson11() {
captureOutput("ExampleJson11") { example.exampleJson11.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"language\":\"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(
- "9042"
+ "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
)
}
@Test
fun testExampleJson13() {
captureOutput("ExampleJson13") { example.exampleJson13.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}"
+ "9042"
)
}
@Test
fun testExampleJson14() {
captureOutput("ExampleJson14") { example.exampleJson14.main() }.verifyOutputLines(
- "Project(name=kotlinx.serialization, language=Kotlin)"
+ "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}"
)
}
@Test
fun testExampleJson15() {
captureOutput("ExampleJson15") { example.exampleJson15.main() }.verifyOutputLines(
- "Project(name=kotlinx.serialization, users=[User(name=kotlin)])",
- "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])"
+ "Project(name=kotlinx.serialization, language=Kotlin)"
)
}
@Test
fun testExampleJson16() {
captureOutput("ExampleJson16") { example.exampleJson16.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}"
+ "Project(name=kotlinx.serialization, users=[User(name=kotlin)])",
+ "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])"
)
}
@Test
fun testExampleJson17() {
captureOutput("ExampleJson17") { example.exampleJson17.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}",
- "{\"name\":\"kotlinx.serialization\"}"
+ "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}"
)
}
@Test
fun testExampleJson18() {
captureOutput("ExampleJson18") { example.exampleJson18.main() }.verifyOutputLines(
- "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]",
- "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]"
+ "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}",
+ "{\"name\":\"kotlinx.serialization\"}"
)
}
@Test
fun testExampleJson19() {
captureOutput("ExampleJson19") { example.exampleJson19.main() }.verifyOutputLines(
- "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]",
- "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]"
+ "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]",
+ "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]"
)
}
@Test
fun testExampleJson20() {
captureOutput("ExampleJson20") { example.exampleJson20.main() }.verifyOutputLines(
+ "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]",
+ "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]"
+ )
+ }
+
+ @Test
+ fun testExampleJson21() {
+ captureOutput("ExampleJson21") { example.exampleJson21.main() }.verifyOutputLines(
"UnknownProject(name=example, details={\"type\":\"unknown\",\"maintainer\":\"Unknown\",\"license\":\"Apache 2.0\"})"
)
}