Skip to content

Commit 194a188

Browse files
authored
Stabilize explicitNulls feature (Kotlin#2661)
- Bring back its interaction with `coerceInputValues` flag - Enhance documentation and add more samples to it - Remove @ExperimentalSerializationApi Fixes Kotlin#2636 Fixes Kotlin#2586
1 parent 53fdc53 commit 194a188

34 files changed

+597
-432
lines changed

docs/json.md

+110-71
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ In this chapter, we'll walk through features of [JSON](https://www.json.org/json
1414
* [Lenient parsing](#lenient-parsing)
1515
* [Ignoring unknown keys](#ignoring-unknown-keys)
1616
* [Alternative Json names](#alternative-json-names)
17-
* [Coercing input values](#coercing-input-values)
1817
* [Encoding defaults](#encoding-defaults)
1918
* [Explicit nulls](#explicit-nulls)
19+
* [Coercing input values](#coercing-input-values)
2020
* [Allowing structured map keys](#allowing-structured-map-keys)
2121
* [Allowing special floating-point values](#allowing-special-floating-point-values)
2222
* [Class discriminator for polymorphism](#class-discriminator-for-polymorphism)
@@ -195,51 +195,6 @@ unless you want to do some fine-tuning.
195195

196196
<!--- TEST -->
197197

198-
### Coercing input values
199-
200-
JSON formats that from third parties can evolve, sometimes changing the field types.
201-
This can lead to exceptions during decoding when the actual values do not match the expected values.
202-
The default [Json] implementation is strict with respect to input types as was demonstrated in
203-
the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section. You can relax this restriction
204-
using the [coerceInputValues][JsonBuilder.coerceInputValues] property.
205-
206-
This property only affects decoding. It treats a limited subset of invalid input values as if the
207-
corresponding property was missing and uses the default value of the corresponding property instead.
208-
The current list of supported invalid values is:
209-
210-
* `null` inputs for non-nullable types
211-
* unknown values for enums
212-
213-
> This list may be expanded in the future, so that [Json] instance configured with this property becomes even more
214-
> permissive to invalid value in the input, replacing them with defaults.
215-
216-
See the example from the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section:
217-
218-
```kotlin
219-
val format = Json { coerceInputValues = true }
220-
221-
@Serializable
222-
data class Project(val name: String, val language: String = "Kotlin")
223-
224-
fun main() {
225-
val data = format.decodeFromString<Project>("""
226-
{"name":"kotlinx.serialization","language":null}
227-
""")
228-
println(data)
229-
}
230-
```
231-
232-
> You can get the full code [here](../guide/example/example-json-05.kt).
233-
234-
The invalid `null` value for the `language` property was coerced into the default value:
235-
236-
```text
237-
Project(name=kotlinx.serialization, language=Kotlin)
238-
```
239-
240-
<!--- TEST -->
241-
242-
243198
### Encoding defaults
244199

245200
Default values of properties are not encoded by default because they will be assigned to missing fields during decoding anyway.
@@ -263,7 +218,7 @@ fun main() {
263218
}
264219
```
265220

266-
> You can get the full code [here](../guide/example/example-json-06.kt).
221+
> You can get the full code [here](../guide/example/example-json-05.kt).
267222
268223
It produces the following output which encodes all the property values including the default ones:
269224

@@ -302,7 +257,7 @@ fun main() {
302257
}
303258
```
304259

305-
> You can get the full code [here](../guide/example/example-json-07.kt).
260+
> You can get the full code [here](../guide/example/example-json-06.kt).
306261
307262
As you can see, `version`, `website` and `description` fields are not present in output JSON on the first line.
308263
After decoding, the missing nullable property `website` without a default values has received a `null` value,
@@ -313,10 +268,94 @@ while nullable properties `version` and `description` are filled with their defa
313268
Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null)
314269
```
315270

271+
> Pay attention to the fact that `version` was `null` before encoding and became `1.2.2` after decoding.
272+
> Encoding/decoding of properties like this — nullable with a non-null default — becomes asymmetrical if `explicitNulls` is set to `false`.
273+
274+
It is possible to make the decoder treat some invalid input data as a missing field to enhance the functionality of this flag.
275+
See [coerceInputValues](#coercing-input-values) below for details.
276+
316277
`explicitNulls` is `true` by default as it is the default behavior across different versions of the library.
317278

318279
<!--- TEST -->
319280

281+
### Coercing input values
282+
283+
JSON formats that from third parties can evolve, sometimes changing the field types.
284+
This can lead to exceptions during decoding when the actual values do not match the expected values.
285+
The default [Json] implementation is strict with respect to input types as was demonstrated in
286+
the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section. You can relax this restriction
287+
using the [coerceInputValues][JsonBuilder.coerceInputValues] property.
288+
289+
This property only affects decoding. It treats a limited subset of invalid input values as if the
290+
corresponding property was missing.
291+
The current list of supported invalid values is:
292+
293+
* `null` inputs for non-nullable types
294+
* unknown values for enums
295+
296+
If value is missing, it is replaced either with a default property value if it exists,
297+
or with a `null` if [explicitNulls](#explicit-nulls) flag is set to `false` and a property is nullable (for enums).
298+
299+
> This list may be expanded in the future, so that [Json] instance configured with this property becomes even more
300+
> permissive to invalid value in the input, replacing them with defaults or nulls.
301+
302+
See the example from the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section:
303+
304+
```kotlin
305+
val format = Json { coerceInputValues = true }
306+
307+
@Serializable
308+
data class Project(val name: String, val language: String = "Kotlin")
309+
310+
fun main() {
311+
val data = format.decodeFromString<Project>("""
312+
{"name":"kotlinx.serialization","language":null}
313+
""")
314+
println(data)
315+
}
316+
```
317+
318+
> You can get the full code [here](../guide/example/example-json-07.kt).
319+
320+
The invalid `null` value for the `language` property was coerced into the default value:
321+
322+
```text
323+
Project(name=kotlinx.serialization, language=Kotlin)
324+
```
325+
326+
<!--- TEST -->
327+
328+
Example of using this flag together with [explicitNulls](#explicit-nulls) to coerce invalid enum values:
329+
330+
```kotlin
331+
enum class Color { BLACK, WHITE }
332+
333+
@Serializable
334+
data class Brush(val foreground: Color = Color.BLACK, val background: Color?)
335+
336+
val json = Json {
337+
coerceInputValues = true
338+
explicitNulls = false
339+
}
340+
341+
fun main() {
342+
val brush = json.decodeFromString<Brush>("""{"foreground":"pink", "background":"purple"}""")
343+
println(brush)
344+
}
345+
```
346+
347+
> You can get the full code [here](../guide/example/example-json-08.kt).
348+
349+
Despite that we do not have `Color.pink` and `Color.purple` colors, `decodeFromString` function returns successfully:
350+
351+
```text
352+
Brush(foreground=BLACK, background=null)
353+
```
354+
355+
`foreground` property received its default value, and `background` property received `null` because of `explicitNulls = false` setting.
356+
357+
<!--- TEST -->
358+
320359
### Allowing structured map keys
321360

322361
JSON format does not natively support the concept of a map with structured keys. Keys in JSON objects
@@ -341,7 +380,7 @@ fun main() {
341380
}
342381
```
343382

344-
> You can get the full code [here](../guide/example/example-json-08.kt).
383+
> You can get the full code [here](../guide/example/example-json-09.kt).
345384
346385
The map with structured keys gets represented as JSON array with the following items: `[key1, value1, key2, value2,...]`.
347386

@@ -372,7 +411,7 @@ fun main() {
372411
}
373412
```
374413

375-
> You can get the full code [here](../guide/example/example-json-09.kt).
414+
> You can get the full code [here](../guide/example/example-json-10.kt).
376415
377416
This example produces the following non-stardard JSON output, yet it is a widely used encoding for
378417
special values in JVM world:
@@ -406,7 +445,7 @@ fun main() {
406445
}
407446
```
408447

409-
> You can get the full code [here](../guide/example/example-json-10.kt).
448+
> You can get the full code [here](../guide/example/example-json-11.kt).
410449
411450
In combination with an explicitly specified [SerialName] of the class it provides full
412451
control over the resulting JSON object:
@@ -462,7 +501,7 @@ fun main() {
462501
}
463502
```
464503

465-
> You can get the full code [here](../guide/example/example-json-11.kt).
504+
> You can get the full code [here](../guide/example/example-json-12.kt).
466505
467506
As you can see, discriminator from the `Base` class is used:
468507

@@ -498,7 +537,7 @@ fun main() {
498537
}
499538
```
500539

501-
> You can get the full code [here](../guide/example/example-json-12.kt).
540+
> You can get the full code [here](../guide/example/example-json-13.kt).
502541
503542
Note that it would be impossible to deserialize this output back with kotlinx.serialization.
504543

@@ -532,7 +571,7 @@ fun main() {
532571
}
533572
```
534573

535-
> You can get the full code [here](../guide/example/example-json-13.kt).
574+
> You can get the full code [here](../guide/example/example-json-14.kt).
536575
537576
It affects serial names as well as alternative names specified with [JsonNames] annotation, so both values are successfully decoded:
538577

@@ -564,7 +603,7 @@ fun main() {
564603
}
565604
```
566605

567-
> You can get the full code [here](../guide/example/example-json-14.kt).
606+
> You can get the full code [here](../guide/example/example-json-15.kt).
568607
569608
As you can see, both serialization and deserialization work as if all serial names are transformed from camel case to snake case:
570609

@@ -662,7 +701,7 @@ fun main() {
662701
}
663702
```
664703

665-
> You can get the full code [here](../guide/example/example-json-15.kt)
704+
> You can get the full code [here](../guide/example/example-json-16.kt)
666705
667706
```text
668707
{"base64Input":"Zm9vIHN0cmluZw=="}
@@ -704,7 +743,7 @@ fun main() {
704743
}
705744
```
706745

707-
> You can get the full code [here](../guide/example/example-json-16.kt).
746+
> You can get the full code [here](../guide/example/example-json-17.kt).
708747
709748
A `JsonElement` prints itself as a valid JSON:
710749

@@ -747,7 +786,7 @@ fun main() {
747786
}
748787
```
749788

750-
> You can get the full code [here](../guide/example/example-json-17.kt).
789+
> You can get the full code [here](../guide/example/example-json-18.kt).
751790
752791
The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`:
753792

@@ -787,7 +826,7 @@ fun main() {
787826
}
788827
```
789828

790-
> You can get the full code [here](../guide/example/example-json-18.kt).
829+
> You can get the full code [here](../guide/example/example-json-19.kt).
791830
792831
As a result, you get a proper JSON string:
793832

@@ -816,7 +855,7 @@ fun main() {
816855
}
817856
```
818857

819-
> You can get the full code [here](../guide/example/example-json-19.kt).
858+
> You can get the full code [here](../guide/example/example-json-20.kt).
820859
821860
The result is exactly what you would expect:
822861

@@ -862,7 +901,7 @@ fun main() {
862901
}
863902
```
864903

865-
> You can get the full code [here](../guide/example/example-json-20.kt).
904+
> You can get the full code [here](../guide/example/example-json-21.kt).
866905
867906
Even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this.
868907
The [Double] value is truncated to 15 decimal places, and the String is wrapped in quotes - which is not a JSON number.
@@ -902,7 +941,7 @@ fun main() {
902941
}
903942
```
904943

905-
> You can get the full code [here](../guide/example/example-json-21.kt).
944+
> You can get the full code [here](../guide/example/example-json-22.kt).
906945
907946
`pi_literal` now accurately matches the value defined.
908947

@@ -942,7 +981,7 @@ fun main() {
942981
}
943982
```
944983

945-
> You can get the full code [here](../guide/example/example-json-22.kt).
984+
> You can get the full code [here](../guide/example/example-json-23.kt).
946985
947986
The exact value of `pi` is decoded, with all 30 decimal places of precision that were in the source JSON.
948987

@@ -964,7 +1003,7 @@ fun main() {
9641003
}
9651004
```
9661005

967-
> You can get the full code [here](../guide/example/example-json-23.kt).
1006+
> You can get the full code [here](../guide/example/example-json-24.kt).
9681007
9691008
```text
9701009
Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive
@@ -1040,7 +1079,7 @@ fun main() {
10401079
}
10411080
```
10421081

1043-
> You can get the full code [here](../guide/example/example-json-24.kt).
1082+
> You can get the full code [here](../guide/example/example-json-25.kt).
10441083
10451084
The output shows that both cases are correctly deserialized into a Kotlin [List].
10461085

@@ -1092,7 +1131,7 @@ fun main() {
10921131
}
10931132
```
10941133

1095-
> You can get the full code [here](../guide/example/example-json-25.kt).
1134+
> You can get the full code [here](../guide/example/example-json-26.kt).
10961135
10971136
You end up with a single JSON object, not an array with one element:
10981137

@@ -1137,7 +1176,7 @@ fun main() {
11371176
}
11381177
```
11391178

1140-
> You can get the full code [here](../guide/example/example-json-26.kt).
1179+
> You can get the full code [here](../guide/example/example-json-27.kt).
11411180
11421181
See the effect of the custom serializer:
11431182

@@ -1210,7 +1249,7 @@ fun main() {
12101249
}
12111250
```
12121251

1213-
> You can get the full code [here](../guide/example/example-json-27.kt).
1252+
> You can get the full code [here](../guide/example/example-json-28.kt).
12141253
12151254
No class discriminator is added in the JSON output:
12161255

@@ -1306,7 +1345,7 @@ fun main() {
13061345
}
13071346
```
13081347

1309-
> You can get the full code [here](../guide/example/example-json-28.kt).
1348+
> You can get the full code [here](../guide/example/example-json-29.kt).
13101349
13111350
This gives you fine-grained control on the representation of the `Response` class in the JSON output:
13121351

@@ -1371,7 +1410,7 @@ fun main() {
13711410
}
13721411
```
13731412

1374-
> You can get the full code [here](../guide/example/example-json-29.kt).
1413+
> You can get the full code [here](../guide/example/example-json-30.kt).
13751414
13761415
```text
13771416
UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"})
@@ -1418,9 +1457,9 @@ The next chapter covers [Alternative and custom formats (experimental)](formats.
14181457
[JsonBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html
14191458
[JsonNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html
14201459
[JsonBuilder.useAlternativeNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html
1421-
[JsonBuilder.coerceInputValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html
14221460
[JsonBuilder.encodeDefaults]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html
14231461
[JsonBuilder.explicitNulls]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html
1462+
[JsonBuilder.coerceInputValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html
14241463
[JsonBuilder.allowStructuredMapKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html
14251464
[JsonBuilder.allowSpecialFloatingPointValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html
14261465
[JsonBuilder.classDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html

docs/serialization-guide.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ Once the project is set up, we can start serializing some classes.
114114
* <a name='lenient-parsing'></a>[Lenient parsing](json.md#lenient-parsing)
115115
* <a name='ignoring-unknown-keys'></a>[Ignoring unknown keys](json.md#ignoring-unknown-keys)
116116
* <a name='alternative-json-names'></a>[Alternative Json names](json.md#alternative-json-names)
117-
* <a name='coercing-input-values'></a>[Coercing input values](json.md#coercing-input-values)
118117
* <a name='encoding-defaults'></a>[Encoding defaults](json.md#encoding-defaults)
119118
* <a name='explicit-nulls'></a>[Explicit nulls](json.md#explicit-nulls)
119+
* <a name='coercing-input-values'></a>[Coercing input values](json.md#coercing-input-values)
120120
* <a name='allowing-structured-map-keys'></a>[Allowing structured map keys](json.md#allowing-structured-map-keys)
121121
* <a name='allowing-special-floating-point-values'></a>[Allowing special floating-point values](json.md#allowing-special-floating-point-values)
122122
* <a name='class-discriminator-for-polymorphism'></a>[Class discriminator for polymorphism](json.md#class-discriminator-for-polymorphism)

0 commit comments

Comments
 (0)