Skip to content

Commit

Permalink
Documentation for 'explicitNulls' feature (#1634)
Browse files Browse the repository at this point in the history
Fixes #1619

Co-authored-by: Sergey.Shanshin <[email protected]>
  • Loading branch information
sandwwraith and shanshin authored Aug 17, 2021
1 parent d00bff2 commit fa569a8
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 218 deletions.
72 changes: 59 additions & 13 deletions docs/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ In this chapter we'll walk through various [Json] features.
* [Alternative Json names](#alternative-json-names)
* [Coercing input values](#coercing-input-values)
* [Encoding defaults](#encoding-defaults)
* [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)
Expand Down Expand Up @@ -264,6 +265,50 @@ It produces the following output which encodes the values of all the properties:

<!--- TEST -->

### Explicit nulls

By default, all `null` values are encoded into JSON string but in some cases one may want them to be omitted.
The encoding of the `null` value can be controlled with the [explicitNulls][JsonBuilder.explicitNulls] property.

When this property is `false`, fields with `null` values are not encoded into JSON, even if the property does not have a default `null` value.
Also, during decoding, the absence of such a value is treated as `null` for nullable properties without a default value.


```kotlin
val format = Json { explicitNulls = false }

@Serializable
data class Project(
val name: String,
val language: String,
val version: String? = "1.2.2",
val website: String?,
val description: String? = null
)

fun main() {
val data = Project("kotlinx.serialization", "Kotlin", null, null, null)
val json = format.encodeToString(data)
println(json)
println(format.decodeFromString<Project>(json))
}
```

> 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.
Also, during decoding, the missing nullable property has received a `null` value
and optional nullable properties are filled with default values.

```text
{"name":"kotlinx.serialization","language":"Kotlin"}
Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null)
```

This flag is set to `true` by default as it is the default behavior across different versions of the library.

<!--- TEST -->

### Allowing structured map keys

JSON format does not natively support the concept of a map with structured keys. Keys in JSON objects
Expand All @@ -286,7 +331,7 @@ fun main() {
}
```

> You can get the full code [here](../guide/example/example-json-07.kt).
> You can get the full code [here](../guide/example/example-json-08.kt).
The map with structured keys gets represented as `[key1, value1, key2, value2,...]` JSON array.

Expand Down Expand Up @@ -317,7 +362,7 @@ fun main() {
}
```

> You can get the full code [here](../guide/example/example-json-08.kt).
> You can get the full code [here](../guide/example/example-json-09.kt).
This example produces the following non-stardard JSON output, yet it is a widely used encoding for
special values in JVM world.
Expand Down Expand Up @@ -351,7 +396,7 @@ fun main() {
}
```

> You can get the full code [here](../guide/example/example-json-09.kt).
> You can get the full code [here](../guide/example/example-json-10.kt).
In combination with an explicitly specified [SerialName] of the class it provides full
control on the resulting JSON object.
Expand Down Expand Up @@ -383,7 +428,7 @@ fun main() {
}
```

> You can get the full code [here](../guide/example/example-json-10.kt).
> You can get the full code [here](../guide/example/example-json-11.kt).
A `JsonElement` prints itself as a valid JSON.

Expand Down Expand Up @@ -426,7 +471,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).
The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`, but
failing if the structure of the data is otherwise different.
Expand Down Expand Up @@ -465,7 +510,7 @@ 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).
At the end, we get a proper JSON string.

Expand Down Expand Up @@ -494,7 +539,7 @@ 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).
The result is exactly what we would expect.

Expand Down Expand Up @@ -571,7 +616,7 @@ 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 output shows that both cases are correctly deserialized into a Kotlin [List].

Expand Down Expand Up @@ -623,7 +668,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).
We end up with a single JSON object.

Expand Down Expand Up @@ -668,7 +713,7 @@ fun main() {
}
```

> 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).
We can clearly see the effect of the custom serializer.

Expand Down Expand Up @@ -741,7 +786,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).
No class discriminator is added in the JSON output.

Expand Down Expand Up @@ -837,7 +882,7 @@ 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).
This gives us fine-grained control on the representation of the `Response` class in our JSON output.

Expand Down Expand Up @@ -902,7 +947,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).
```text
UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"})
Expand Down Expand Up @@ -943,6 +988,7 @@ The next chapter covers [Alternative and custom formats (experimental)](formats.
[JsonBuilder.useAlternativeNames]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FuseAlternativeNames%2F%23%2FPointingToDeclaration%2F
[JsonBuilder.coerceInputValues]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FcoerceInputValues%2F%23%2FPointingToDeclaration%2F
[JsonBuilder.encodeDefaults]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FencodeDefaults%2F%23%2FPointingToDeclaration%2F
[JsonBuilder.explicitNulls]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FexplicitNulls%2F%23%2FPointingToDeclaration%2F
[JsonBuilder.allowStructuredMapKeys]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FallowStructuredMapKeys%2F%23%2FPointingToDeclaration%2F
[JsonBuilder.allowSpecialFloatingPointValues]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FallowSpecialFloatingPointValues%2F%23%2FPointingToDeclaration%2F
[JsonBuilder.classDiscriminator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html#kotlinx.serialization.json%2FJsonBuilder%2FclassDiscriminator%2F%23%2FPointingToDeclaration%2F
Expand Down
1 change: 1 addition & 0 deletions docs/serialization-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Once the project is set up, we can start serializing some classes.
* <a name='alternative-json-names'></a>[Alternative Json names](json.md#alternative-json-names)
* <a name='coercing-input-values'></a>[Coercing input values](json.md#coercing-input-values)
* <a name='encoding-defaults'></a>[Encoding defaults](json.md#encoding-defaults)
* <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)
Expand Down
25 changes: 15 additions & 10 deletions guide/example/example-json-07.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ package example.exampleJson07
import kotlinx.serialization.*
import kotlinx.serialization.json.*

val format = Json { allowStructuredMapKeys = true }
val format = Json { explicitNulls = false }

@Serializable
data class Project(val name: String)

fun main() {
val map = mapOf(
Project("kotlinx.serialization") to "Serialization",
Project("kotlinx.coroutines") to "Coroutines"
)
println(format.encodeToString(map))
@Serializable
data class Project(
val name: String,
val language: String,
val version: String? = "1.2.2",
val website: String?,
val description: String? = null
)

fun main() {
val data = Project("kotlinx.serialization", "Kotlin", null, null, null)
val json = format.encodeToString(data)
println(json)
println(format.decodeFromString<Project>(json))
}
19 changes: 10 additions & 9 deletions guide/example/example-json-08.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ package example.exampleJson08
import kotlinx.serialization.*
import kotlinx.serialization.json.*

val format = Json { allowSpecialFloatingPointValues = true }
val format = Json { allowStructuredMapKeys = true }

@Serializable
class Data(
val value: Double
)

fun main() {
val data = Data(Double.NaN)
println(format.encodeToString(data))
@Serializable
data class Project(val name: String)

fun main() {
val map = mapOf(
Project("kotlinx.serialization") to "Serialization",
Project("kotlinx.coroutines") to "Coroutines"
)
println(format.encodeToString(map))
}
16 changes: 6 additions & 10 deletions guide/example/example-json-09.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@ package example.exampleJson09
import kotlinx.serialization.*
import kotlinx.serialization.json.*

val format = Json { classDiscriminator = "#class" }
val format = Json { allowSpecialFloatingPointValues = true }

@Serializable
sealed class Project {
abstract val name: String
}

@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()
class Data(
val value: Double
)

fun main() {
val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
val data = Data(Double.NaN)
println(format.encodeToString(data))
}
}
19 changes: 14 additions & 5 deletions guide/example/example-json-10.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ package example.exampleJson10
import kotlinx.serialization.*
import kotlinx.serialization.json.*

fun main() {
val element = Json.parseToJsonElement("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
println(element)
val format = Json { classDiscriminator = "#class" }

@Serializable
sealed class Project {
abstract val name: String
}

@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
println(format.encodeToString(data))
}
10 changes: 2 additions & 8 deletions guide/example/example-json-11.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
23 changes: 9 additions & 14 deletions guide/example/example-json-12.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
18 changes: 12 additions & 6 deletions guide/example/example-json-13.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ package example.exampleJson13
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)
}
Loading

0 comments on commit fa569a8

Please sign in to comment.