1<!--- TEST_NAME JsonTest --> 2 3# JSON features 4 5This is the fifth chapter of the [Kotlin Serialization Guide](serialization-guide.md). 6In this chapter, we'll walk through features of [JSON](https://www.json.org/json-en.html) serialization available in the [Json] class. 7 8**Table of contents** 9 10<!--- TOC --> 11 12* [Json configuration](#json-configuration) 13 * [Pretty printing](#pretty-printing) 14 * [Lenient parsing](#lenient-parsing) 15 * [Ignoring unknown keys](#ignoring-unknown-keys) 16 * [Alternative Json names](#alternative-json-names) 17 * [Encoding defaults](#encoding-defaults) 18 * [Explicit nulls](#explicit-nulls) 19 * [Coercing input values](#coercing-input-values) 20 * [Allowing structured map keys](#allowing-structured-map-keys) 21 * [Allowing special floating-point values](#allowing-special-floating-point-values) 22 * [Class discriminator for polymorphism](#class-discriminator-for-polymorphism) 23 * [Class discriminator output mode](#class-discriminator-output-mode) 24 * [Decoding enums in a case-insensitive manner](#decoding-enums-in-a-case-insensitive-manner) 25 * [Global naming strategy](#global-naming-strategy) 26 * [Base64](#base64) 27* [Json elements](#json-elements) 28 * [Parsing to Json element](#parsing-to-json-element) 29 * [Types of Json elements](#types-of-json-elements) 30 * [Json element builders](#json-element-builders) 31 * [Decoding Json elements](#decoding-json-elements) 32 * [Encoding literal Json content (experimental)](#encoding-literal-json-content-experimental) 33 * [Serializing large decimal numbers](#serializing-large-decimal-numbers) 34 * [Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden](#using-jsonunquotedliteral-to-create-a-literal-unquoted-value-of-null-is-forbidden) 35* [Json transformations](#json-transformations) 36 * [Array wrapping](#array-wrapping) 37 * [Array unwrapping](#array-unwrapping) 38 * [Manipulating default values](#manipulating-default-values) 39 * [Content-based polymorphic deserialization](#content-based-polymorphic-deserialization) 40 * [Extending the behavior of the plugin generated serializer](#extending-the-behavior-of-the-plugin-generated-serializer) 41 * [Under the hood (experimental)](#under-the-hood-experimental) 42 * [Maintaining custom JSON attributes](#maintaining-custom-json-attributes) 43 44<!--- END --> 45 46## Json configuration 47 48The default [Json] implementation is quite strict with respect to invalid inputs. It enforces Kotlin type safety and 49restricts Kotlin values that can be serialized so that the resulting JSON representations are standard. 50Many non-standard JSON features are supported by creating a custom instance of a JSON _format_. 51 52To use a custom JSON format configuration, create your own [Json] class instance from an existing 53instance, such as a default `Json` object, using the [Json()] builder function. Specify parameter values 54in the parentheses via the [JsonBuilder] DSL. The resulting `Json` format instance is immutable and thread-safe; 55it can be simply stored in a top-level property. 56 57> We recommend that you store and reuse custom instances of formats for performance reasons because format implementations 58> may cache format-specific additional information about the classes they serialize. 59 60This chapter shows configuration features that [Json] supports. 61 62<!--- INCLUDE .*-json-.* 63import kotlinx.serialization.* 64import kotlinx.serialization.json.* 65--> 66 67### Pretty printing 68 69By default, the [Json] output is a single line. You can configure it to pretty print the output (that is, add indentations 70and line breaks for better readability) by setting the [prettyPrint][JsonBuilder.prettyPrint] property to `true`: 71 72```kotlin 73val format = Json { prettyPrint = true } 74 75@Serializable 76data class Project(val name: String, val language: String) 77 78fun main() { 79 val data = Project("kotlinx.serialization", "Kotlin") 80 println(format.encodeToString(data)) 81} 82``` 83 84> You can get the full code [here](../guide/example/example-json-01.kt). 85 86It gives the following nice result: 87 88```text 89{ 90 "name": "kotlinx.serialization", 91 "language": "Kotlin" 92} 93``` 94 95<!--- TEST --> 96 97### Lenient parsing 98 99By default, [Json] parser enforces various JSON restrictions to be as specification-compliant as possible 100(see [RFC-4627]). Particularly, keys and string literals must be quoted. Those restrictions can be relaxed with 101the [isLenient][JsonBuilder.isLenient] property. With `isLenient = true`, you can parse quite freely-formatted data: 102 103```kotlin 104val format = Json { isLenient = true } 105 106enum class Status { SUPPORTED } 107 108@Serializable 109data class Project(val name: String, val status: Status, val votes: Int) 110 111fun main() { 112 val data = format.decodeFromString<Project>(""" 113 { 114 name : kotlinx.serialization, 115 status : SUPPORTED, 116 votes : "9000" 117 } 118 """) 119 println(data) 120} 121``` 122 123> You can get the full code [here](../guide/example/example-json-02.kt). 124 125You get the object, even though all keys of the source JSON, string and enum values are unquoted: 126 127```text 128Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000) 129``` 130 131> Note that parsing of quoted numbers or booleans such as `votes: "9000"` to `val votes: Int` is generally allowed by kotlinx.serialization 132> regardless of the `isLenient` flag, since such JSON is syntactically valid. 133 134<!--- TEST --> 135 136### Ignoring unknown keys 137 138JSON format is often used to read the output of third-party services or in other dynamic environments where 139new properties can be added during the API evolution. By default, unknown keys encountered during deserialization produce an error. 140You can avoid this and just ignore such keys by setting the [ignoreUnknownKeys][JsonBuilder.ignoreUnknownKeys] property 141to `true`: 142 143```kotlin 144val format = Json { ignoreUnknownKeys = true } 145 146@Serializable 147data class Project(val name: String) 148 149fun main() { 150 val data = format.decodeFromString<Project>(""" 151 {"name":"kotlinx.serialization","language":"Kotlin"} 152 """) 153 println(data) 154} 155``` 156 157> You can get the full code [here](../guide/example/example-json-03.kt). 158 159It decodes the object despite the fact that the `Project` class doesn't have the `language` property: 160 161```text 162Project(name=kotlinx.serialization) 163``` 164 165<!--- TEST --> 166 167### Alternative Json names 168 169It's not a rare case when JSON fields are renamed due to a schema version change. 170You can use the [`@SerialName` annotation](basic-serialization.md#serial-field-names) to change the name of a JSON field, 171but such renaming blocks the ability to decode data with the old name. 172To support multiple JSON names for the one Kotlin property, there is the [JsonNames] annotation: 173 174```kotlin 175@OptIn(ExperimentalSerializationApi::class) // JsonNames is an experimental annotation for now 176@Serializable 177data class Project(@JsonNames("title") val name: String) 178 179fun main() { 180 val project = Json.decodeFromString<Project>("""{"name":"kotlinx.serialization"}""") 181 println(project) 182 val oldProject = Json.decodeFromString<Project>("""{"title":"kotlinx.coroutines"}""") 183 println(oldProject) 184} 185``` 186 187> You can get the full code [here](../guide/example/example-json-04.kt). 188 189As you can see, both `name` and `title` Json fields correspond to `name` property: 190 191```text 192Project(name=kotlinx.serialization) 193Project(name=kotlinx.coroutines) 194``` 195 196Support for [JsonNames] annotation is controlled by the [JsonBuilder.useAlternativeNames] flag. 197Unlike most of the configuration flags, this one is enabled by default and does not need attention 198unless you want to do some fine-tuning. 199 200<!--- TEST --> 201 202### Encoding defaults 203 204Default values of properties are not encoded by default because they will be assigned to missing fields during decoding anyway. 205See the [Defaults are not encoded](basic-serialization.md#defaults-are-not-encoded-by-default) section for details and an example. 206This is especially useful for nullable properties with null defaults and avoids writing the corresponding null values. 207The default behavior can be changed by setting the [encodeDefaults][JsonBuilder.encodeDefaults] property to `true`: 208 209```kotlin 210val format = Json { encodeDefaults = true } 211 212@Serializable 213class Project( 214 val name: String, 215 val language: String = "Kotlin", 216 val website: String? = null 217) 218 219fun main() { 220 val data = Project("kotlinx.serialization") 221 println(format.encodeToString(data)) 222} 223``` 224 225> You can get the full code [here](../guide/example/example-json-05.kt). 226 227It produces the following output which encodes all the property values including the default ones: 228 229```text 230{"name":"kotlinx.serialization","language":"Kotlin","website":null} 231``` 232 233<!--- TEST --> 234 235### Explicit nulls 236 237By default, all `null` values are encoded into JSON strings, but in some cases you may want to omit them. 238The encoding of `null` values can be controlled with the [explicitNulls][JsonBuilder.explicitNulls] property. 239 240If you set property to `false`, fields with `null` values are not encoded into JSON even if the property does not have a 241default `null` value. When decoding such JSON, the absence of a property value is treated as `null` for nullable properties 242without a default value. 243 244```kotlin 245val format = Json { explicitNulls = false } 246 247@Serializable 248data class Project( 249 val name: String, 250 val language: String, 251 val version: String? = "1.2.2", 252 val website: String?, 253 val description: String? = null 254) 255 256fun main() { 257 val data = Project("kotlinx.serialization", "Kotlin", null, null, null) 258 val json = format.encodeToString(data) 259 println(json) 260 println(format.decodeFromString<Project>(json)) 261} 262``` 263 264> You can get the full code [here](../guide/example/example-json-06.kt). 265 266As you can see, `version`, `website` and `description` fields are not present in output JSON on the first line. 267After decoding, the missing nullable property `website` without a default values has received a `null` value, 268while nullable properties `version` and `description` are filled with their default values: 269 270```text 271{"name":"kotlinx.serialization","language":"Kotlin"} 272Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null) 273``` 274 275> Pay attention to the fact that `version` was `null` before encoding and became `1.2.2` after decoding. 276> Encoding/decoding of properties like this — nullable with a non-null default — becomes asymmetrical if `explicitNulls` is set to `false`. 277 278It is possible to make the decoder treat some invalid input data as a missing field to enhance the functionality of this flag. 279See [coerceInputValues](#coercing-input-values) below for details. 280 281`explicitNulls` is `true` by default as it is the default behavior across different versions of the library. 282 283<!--- TEST --> 284 285### Coercing input values 286 287JSON formats that from third parties can evolve, sometimes changing the field types. 288This can lead to exceptions during decoding when the actual values do not match the expected values. 289The default [Json] implementation is strict with respect to input types as was demonstrated in 290the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section. You can relax this restriction 291using the [coerceInputValues][JsonBuilder.coerceInputValues] property. 292 293This property only affects decoding. It treats a limited subset of invalid input values as if the 294corresponding property was missing. 295The current list of supported invalid values is: 296 297* `null` inputs for non-nullable types 298* unknown values for enums 299 300If value is missing, it is replaced either with a default property value if it exists, 301or with a `null` if [explicitNulls](#explicit-nulls) flag is set to `false` and a property is nullable (for enums). 302 303> This list may be expanded in the future, so that [Json] instance configured with this property becomes even more 304> permissive to invalid value in the input, replacing them with defaults or nulls. 305 306See the example from the [Type safety is enforced](basic-serialization.md#type-safety-is-enforced) section: 307 308```kotlin 309val format = Json { coerceInputValues = true } 310 311@Serializable 312data class Project(val name: String, val language: String = "Kotlin") 313 314fun main() { 315 val data = format.decodeFromString<Project>(""" 316 {"name":"kotlinx.serialization","language":null} 317 """) 318 println(data) 319} 320``` 321 322> You can get the full code [here](../guide/example/example-json-07.kt). 323 324The invalid `null` value for the `language` property was coerced into the default value: 325 326```text 327Project(name=kotlinx.serialization, language=Kotlin) 328``` 329 330<!--- TEST --> 331 332Example of using this flag together with [explicitNulls](#explicit-nulls) to coerce invalid enum values: 333 334```kotlin 335enum class Color { BLACK, WHITE } 336 337@Serializable 338data class Brush(val foreground: Color = Color.BLACK, val background: Color?) 339 340val json = Json { 341 coerceInputValues = true 342 explicitNulls = false 343} 344 345fun main() { 346 val brush = json.decodeFromString<Brush>("""{"foreground":"pink", "background":"purple"}""") 347 println(brush) 348} 349``` 350 351> You can get the full code [here](../guide/example/example-json-08.kt). 352 353Despite that we do not have `Color.pink` and `Color.purple` colors, `decodeFromString` function returns successfully: 354 355```text 356Brush(foreground=BLACK, background=null) 357``` 358 359`foreground` property received its default value, and `background` property received `null` because of `explicitNulls = false` setting. 360 361<!--- TEST --> 362 363### Allowing structured map keys 364 365JSON format does not natively support the concept of a map with structured keys. Keys in JSON objects 366are strings and can be used to represent only primitives or enums by default. 367You can enable non-standard support for structured keys with 368the [allowStructuredMapKeys][JsonBuilder.allowStructuredMapKeys] property. 369 370This is how you can serialize a map with keys of a user-defined class: 371 372```kotlin 373val format = Json { allowStructuredMapKeys = true } 374 375@Serializable 376data class Project(val name: String) 377 378fun main() { 379 val map = mapOf( 380 Project("kotlinx.serialization") to "Serialization", 381 Project("kotlinx.coroutines") to "Coroutines" 382 ) 383 println(format.encodeToString(map)) 384} 385``` 386 387> You can get the full code [here](../guide/example/example-json-09.kt). 388 389The map with structured keys gets represented as JSON array with the following items: `[key1, value1, key2, value2,...]`. 390 391```text 392[{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"] 393``` 394 395<!--- TEST --> 396 397### Allowing special floating-point values 398 399By default, special floating-point values like [Double.NaN] and infinities are not supported in JSON because 400the JSON specification prohibits it. 401You can enable their encoding using the [allowSpecialFloatingPointValues][JsonBuilder.allowSpecialFloatingPointValues] 402property: 403 404```kotlin 405val format = Json { allowSpecialFloatingPointValues = true } 406 407@Serializable 408class Data( 409 val value: Double 410) 411 412fun main() { 413 val data = Data(Double.NaN) 414 println(format.encodeToString(data)) 415} 416``` 417 418> You can get the full code [here](../guide/example/example-json-10.kt). 419 420This example produces the following non-stardard JSON output, yet it is a widely used encoding for 421special values in JVM world: 422 423```text 424{"value":NaN} 425``` 426 427<!--- TEST --> 428 429### Class discriminator for polymorphism 430 431A key name that specifies a type when you have a polymorphic data can be specified 432in the [classDiscriminator][JsonBuilder.classDiscriminator] property: 433 434```kotlin 435val format = Json { classDiscriminator = "#class" } 436 437@Serializable 438sealed class Project { 439 abstract val name: String 440} 441 442@Serializable 443@SerialName("owned") 444class OwnedProject(override val name: String, val owner: String) : Project() 445 446fun main() { 447 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 448 println(format.encodeToString(data)) 449} 450``` 451 452> You can get the full code [here](../guide/example/example-json-11.kt). 453 454In combination with an explicitly specified [SerialName] of the class it provides full 455control over the resulting JSON object: 456 457```text 458{"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"} 459``` 460 461<!--- TEST --> 462 463It is also possible to specify different class discriminators for different hierarchies. Instead of Json instance property, use [JsonClassDiscriminator] annotation directly on base serializable class: 464 465```kotlin 466@OptIn(ExperimentalSerializationApi::class) // JsonClassDiscriminator is an experimental annotation for now 467@Serializable 468@JsonClassDiscriminator("message_type") 469sealed class Base 470``` 471 472This annotation is _inheritable_, so all subclasses of `Base` will have the same discriminator: 473 474```kotlin 475@Serializable // Class discriminator is inherited from Base 476sealed class ErrorClass: Base() 477``` 478 479> To learn more about inheritable serial annotations, see documentation for [InheritableSerialInfo]. 480 481Note that it is not possible to explicitly specify different class discriminators in subclasses of `Base`. Only hierarchies with empty intersections can have different discriminators. 482 483Discriminator specified in the annotation has priority over discriminator in Json configuration: 484 485<!--- INCLUDE 486 487@Serializable 488data class Message(val message: Base, val error: ErrorClass?) 489 490@Serializable 491@SerialName("my.app.BaseMessage") 492data class BaseMessage(val message: String) : Base() 493 494@Serializable 495@SerialName("my.app.GenericError") 496data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() 497--> 498 499```kotlin 500 501val format = Json { classDiscriminator = "#class" } 502 503fun main() { 504 val data = Message(BaseMessage("not found"), GenericError(404)) 505 println(format.encodeToString(data)) 506} 507``` 508 509> You can get the full code [here](../guide/example/example-json-12.kt). 510 511As you can see, discriminator from the `Base` class is used: 512 513```text 514{"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}} 515``` 516 517<!--- TEST --> 518 519### Class discriminator output mode 520 521Class discriminator provides information for serializing and deserializing [polymorphic class hierarchies](polymorphism.md#sealed-classes). 522As shown above, it is only added for polymorphic classes by default. 523In case you want to encode more or less information for various third party APIs about types in the output, it is possible to control 524addition of the class discriminator with the [JsonBuilder.classDiscriminatorMode] property. 525 526For example, [ClassDiscriminatorMode.NONE] does not add class discriminator at all, in case the receiving party is not interested in Kotlin types: 527 528```kotlin 529@OptIn(ExperimentalSerializationApi::class) // classDiscriminatorMode is an experimental setting for now 530val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE } 531 532@Serializable 533sealed class Project { 534 abstract val name: String 535} 536 537@Serializable 538class OwnedProject(override val name: String, val owner: String) : Project() 539 540fun main() { 541 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 542 println(format.encodeToString(data)) 543} 544``` 545 546> You can get the full code [here](../guide/example/example-json-13.kt). 547 548Note that it would be impossible to deserialize this output back with kotlinx.serialization. 549 550```text 551{"name":"kotlinx.coroutines","owner":"kotlin"} 552``` 553 554Two other available values are [ClassDiscriminatorMode.POLYMORPHIC] (default behavior) and [ClassDiscriminatorMode.ALL_JSON_OBJECTS] (adds discriminator whenever possible). 555Consult their documentation for details. 556 557<!--- TEST --> 558 559### Decoding enums in a case-insensitive manner 560 561[Kotlin's naming policy recommends](https://kotlinlang.org/docs/coding-conventions.html#property-names) naming enum values 562using either uppercase underscore-separated names or upper camel case names. 563[Json] uses exact Kotlin enum values names for decoding by default. 564However, sometimes third-party JSONs have such values named in lowercase or some mixed case. 565In this case, it is possible to decode enum values in a case-insensitive manner using [JsonBuilder.decodeEnumsCaseInsensitive] property: 566 567```kotlin 568@OptIn(ExperimentalSerializationApi::class) // decodeEnumsCaseInsensitive is an experimental setting for now 569val format = Json { decodeEnumsCaseInsensitive = true } 570 571@OptIn(ExperimentalSerializationApi::class) // JsonNames is an experimental annotation for now 572enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B } 573 574@Serializable 575data class CasesList(val cases: List<Cases>) 576 577fun main() { 578 println(format.decodeFromString<CasesList>("""{"cases":["value_A", "alternative"]}""")) 579} 580``` 581 582> You can get the full code [here](../guide/example/example-json-14.kt). 583 584It affects serial names as well as alternative names specified with [JsonNames] annotation, so both values are successfully decoded: 585 586```text 587CasesList(cases=[VALUE_A, VALUE_B]) 588``` 589 590This property does not affect encoding in any way. 591 592<!--- TEST --> 593 594### Global naming strategy 595 596If properties' names in Json input are different from Kotlin ones, it is recommended to specify the name 597for each property explicitly using [`@SerialName` annotation](basic-serialization.md#serial-field-names). 598However, there are certain situations where transformation should be applied to every serial name — such as migration 599from other frameworks or legacy codebase. For that cases, it is possible to specify a [namingStrategy][JsonBuilder.namingStrategy] 600for a [Json] instance. `kotlinx.serialization` provides one strategy implementation out of the box, the [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html): 601 602```kotlin 603@Serializable 604data class Project(val projectName: String, val projectOwner: String) 605 606@OptIn(ExperimentalSerializationApi::class) // namingStrategy is an experimental setting for now 607val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase } 608 609fun main() { 610 val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""") 611 println(format.encodeToString(project.copy(projectName = "kotlinx.serialization"))) 612} 613``` 614 615> You can get the full code [here](../guide/example/example-json-15.kt). 616 617As you can see, both serialization and deserialization work as if all serial names are transformed from camel case to snake case: 618 619```text 620{"project_name":"kotlinx.serialization","project_owner":"Kotlin"} 621``` 622 623There are some caveats one should remember while dealing with a [JsonNamingStrategy]: 624 625* Due to the nature of the `kotlinx.serialization` framework, naming strategy transformation is applied to all properties regardless 626of whether their serial name was taken from the property name or provided by [SerialName] annotation. 627Effectively, it means one cannot avoid transformation by explicitly specifying the serial name. To be able to deserialize 628non-transformed names, [JsonNames] annotation can be used instead. 629 630* Collision of the transformed name with any other (transformed) properties serial names or any alternative names 631specified with [JsonNames] will lead to a deserialization exception. 632 633* Global naming strategies are very implicit: by looking only at the definition of the class, 634it is impossible to determine which names it will have in the serialized form. 635As a consequence, naming strategies are not friendly to actions like Find Usages/Rename in IDE, full-text search by grep, etc. 636For them, the original name and the transformed are two different things; 637changing one without the other may introduce bugs in many unexpected ways and lead to greater maintenance efforts for code with global naming strategies. 638 639Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application. 640 641<!--- TEST --> 642 643### Base64 644 645To encode and decode Base64 formats, we will need to manually write a serializer. Here, we will use a default 646implementation of Kotlin's Base64 encoder. Note that some serializers use different RFCs for Base64 encoding by default. 647For example, Jackson uses a variant of [Base64 Mime](https://datatracker.ietf.org/doc/html/rfc2045). The same result in 648kotlinx.serialization can be achieved with Base64.Mime encoder. 649[Kotlin's documentation for Base64](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.encoding/-base64/) lists 650other available encoders. 651 652```kotlin 653import kotlinx.serialization.encoding.Encoder 654import kotlinx.serialization.encoding.Decoder 655import kotlinx.serialization.descriptors.* 656import kotlin.io.encoding.* 657 658@OptIn(ExperimentalEncodingApi::class) 659object ByteArrayAsBase64Serializer : KSerializer<ByteArray> { 660 private val base64 = Base64.Default 661 662 override val descriptor: SerialDescriptor 663 get() = PrimitiveSerialDescriptor( 664 "ByteArrayAsBase64Serializer", 665 PrimitiveKind.STRING 666 ) 667 668 override fun serialize(encoder: Encoder, value: ByteArray) { 669 val base64Encoded = base64.encode(value) 670 encoder.encodeString(base64Encoded) 671 } 672 673 override fun deserialize(decoder: Decoder): ByteArray { 674 val base64Decoded = decoder.decodeString() 675 return base64.decode(base64Decoded) 676 } 677} 678``` 679 680For more details on how to create your own custom serializer, you can 681see [custom serializers](serializers.md#custom-serializers). 682 683Then we can use it like this: 684 685```kotlin 686@Serializable 687data class Value( 688 @Serializable(with = ByteArrayAsBase64Serializer::class) 689 val base64Input: ByteArray 690) { 691 override fun equals(other: Any?): Boolean { 692 if (this === other) return true 693 if (javaClass != other?.javaClass) return false 694 other as Value 695 return base64Input.contentEquals(other.base64Input) 696 } 697 698 override fun hashCode(): Int { 699 return base64Input.contentHashCode() 700 } 701} 702 703fun main() { 704 val string = "foo string" 705 val value = Value(string.toByteArray()) 706 val encoded = Json.encodeToString(value) 707 println(encoded) 708 val decoded = Json.decodeFromString<Value>(encoded) 709 println(decoded.base64Input.decodeToString()) 710} 711``` 712 713> You can get the full code [here](../guide/example/example-json-16.kt) 714 715```text 716{"base64Input":"Zm9vIHN0cmluZw=="} 717foo string 718``` 719 720Notice the serializer we wrote is not dependent on `Json` format, therefore, it can be used in any format. 721 722For projects that use this serializer in many places, to avoid specifying the serializer every time, it is possible 723to [specify a serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias). 724For example: 725````kotlin 726typealias Base64ByteArray = @Serializable(ByteArrayAsBase64Serializer::class) ByteArray 727```` 728 729<!--- TEST --> 730 731## Json elements 732 733Aside from direct conversions between strings and JSON objects, Kotlin serialization offers APIs that allow 734other ways of working with JSON in the code. For example, you might need to tweak the data before it can parse 735or otherwise work with such an unstructured data that it does not readily fit into the typesafe world of Kotlin 736serialization. 737 738The main concept in this part of the library is [JsonElement]. Read on to learn what you can do with it. 739 740### Parsing to Json element 741 742A string can be _parsed_ into an instance of [JsonElement] with the [Json.parseToJsonElement] function. 743It is called neither decoding nor deserialization because none of that happens in the process. 744It just parses a JSON and forms an object representing it: 745 746```kotlin 747fun main() { 748 val element = Json.parseToJsonElement(""" 749 {"name":"kotlinx.serialization","language":"Kotlin"} 750 """) 751 println(element) 752} 753``` 754 755> You can get the full code [here](../guide/example/example-json-17.kt). 756 757A `JsonElement` prints itself as a valid JSON: 758 759```text 760{"name":"kotlinx.serialization","language":"Kotlin"} 761``` 762 763<!--- TEST --> 764 765### Types of Json elements 766 767A [JsonElement] class has three direct subtypes, closely following JSON grammar: 768 769* [JsonPrimitive] represents primitive JSON elements, such as string, number, boolean, and null. 770 Each primitive has a simple string [content][JsonPrimitive.content]. There is also a 771 [JsonPrimitive()] constructor function overloaded to accept various primitive Kotlin types and 772 to convert them to `JsonPrimitive`. 773 774* [JsonArray] represents a JSON `[...]` array. It is a Kotlin [List] of `JsonElement` items. 775 776* [JsonObject] represents a JSON `{...}` object. It is a Kotlin [Map] from `String` keys to `JsonElement` values. 777 778The `JsonElement` class has extensions that cast it to its corresponding subtypes: 779[jsonPrimitive][_jsonPrimitive], [jsonArray][_jsonArray], [jsonObject][_jsonObject]. The `JsonPrimitive` class, 780in turn, provides converters to Kotlin primitive types: [int], [intOrNull], [long], [longOrNull], 781and similar ones for other types. This is how you can use them for processing JSON whose structure you know: 782 783```kotlin 784fun main() { 785 val element = Json.parseToJsonElement(""" 786 { 787 "name": "kotlinx.serialization", 788 "forks": [{"votes": 42}, {"votes": 9000}, {}] 789 } 790 """) 791 val sum = element 792 .jsonObject["forks"]!! 793 .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 } 794 println(sum) 795} 796``` 797 798> You can get the full code [here](../guide/example/example-json-18.kt). 799 800The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`: 801 802```text 8039042 804``` 805 806<!--- TEST --> 807 808Note that the execution will fail if the structure of the data is otherwise different. 809 810### Json element builders 811 812You can construct instances of specific [JsonElement] subtypes using the respective builder functions 813[buildJsonArray] and [buildJsonObject]. They provide a DSL to define the resulting JSON structure. It 814is similar to Kotlin standard library collection builders, but with a JSON-specific convenience 815of more type-specific overloads and inner builder functions. The following example shows 816all the key features: 817 818```kotlin 819fun main() { 820 val element = buildJsonObject { 821 put("name", "kotlinx.serialization") 822 putJsonObject("owner") { 823 put("name", "kotlin") 824 } 825 putJsonArray("forks") { 826 addJsonObject { 827 put("votes", 42) 828 } 829 addJsonObject { 830 put("votes", 9000) 831 } 832 } 833 } 834 println(element) 835} 836``` 837 838> You can get the full code [here](../guide/example/example-json-19.kt). 839 840As a result, you get a proper JSON string: 841 842```text 843{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} 844``` 845 846<!--- TEST --> 847 848### Decoding Json elements 849 850An instance of the [JsonElement] class can be decoded into a serializable object using 851the [Json.decodeFromJsonElement] function: 852 853```kotlin 854@Serializable 855data class Project(val name: String, val language: String) 856 857fun main() { 858 val element = buildJsonObject { 859 put("name", "kotlinx.serialization") 860 put("language", "Kotlin") 861 } 862 val data = Json.decodeFromJsonElement<Project>(element) 863 println(data) 864} 865``` 866 867> You can get the full code [here](../guide/example/example-json-20.kt). 868 869The result is exactly what you would expect: 870 871```text 872Project(name=kotlinx.serialization, language=Kotlin) 873``` 874 875<!--- TEST --> 876 877### Encoding literal Json content (experimental) 878 879> This functionality is experimental and requires opting-in to [the experimental Kotlinx Serialization API](compatibility.md#experimental-api). 880 881In some cases it might be necessary to encode an arbitrary unquoted value. 882This can be achieved with [JsonUnquotedLiteral]. 883 884#### Serializing large decimal numbers 885 886The JSON specification does not restrict the size or precision of numbers, however it is not possible to serialize 887numbers of arbitrary size or precision using [JsonPrimitive()]. 888 889If [Double] is used, then the numbers are limited in precision, meaning that large numbers are truncated. 890When using Kotlin/JVM [BigDecimal] can be used instead, but [JsonPrimitive()] will encode the value as a string, not a 891number. 892 893```kotlin 894import java.math.BigDecimal 895 896val format = Json { prettyPrint = true } 897 898fun main() { 899 val pi = BigDecimal("3.141592653589793238462643383279") 900 901 val piJsonDouble = JsonPrimitive(pi.toDouble()) 902 val piJsonString = JsonPrimitive(pi.toString()) 903 904 val piObject = buildJsonObject { 905 put("pi_double", piJsonDouble) 906 put("pi_string", piJsonString) 907 } 908 909 println(format.encodeToString(piObject)) 910} 911``` 912 913> You can get the full code [here](../guide/example/example-json-21.kt). 914 915Even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. 916The [Double] value is truncated to 15 decimal places, and the String is wrapped in quotes - which is not a JSON number. 917 918```text 919{ 920 "pi_double": 3.141592653589793, 921 "pi_string": "3.141592653589793238462643383279" 922} 923``` 924 925<!--- TEST --> 926 927To avoid precision loss, the string value of `pi` can be encoded using [JsonUnquotedLiteral]. 928 929```kotlin 930import java.math.BigDecimal 931 932val format = Json { prettyPrint = true } 933 934fun main() { 935 val pi = BigDecimal("3.141592653589793238462643383279") 936 937 // use JsonUnquotedLiteral to encode raw JSON content 938 @OptIn(ExperimentalSerializationApi::class) 939 val piJsonLiteral = JsonUnquotedLiteral(pi.toString()) 940 941 val piJsonDouble = JsonPrimitive(pi.toDouble()) 942 val piJsonString = JsonPrimitive(pi.toString()) 943 944 val piObject = buildJsonObject { 945 put("pi_literal", piJsonLiteral) 946 put("pi_double", piJsonDouble) 947 put("pi_string", piJsonString) 948 } 949 950 println(format.encodeToString(piObject)) 951} 952``` 953 954> You can get the full code [here](../guide/example/example-json-22.kt). 955 956`pi_literal` now accurately matches the value defined. 957 958```text 959{ 960 "pi_literal": 3.141592653589793238462643383279, 961 "pi_double": 3.141592653589793, 962 "pi_string": "3.141592653589793238462643383279" 963} 964``` 965 966<!--- TEST --> 967 968To decode `pi` back to a [BigDecimal], the string content of the [JsonPrimitive] can be used. 969 970(This demonstration uses a [JsonPrimitive] for simplicity. For a more re-usable method of handling serialization, see 971[Json Transformations](#json-transformations) below.) 972 973 974```kotlin 975import java.math.BigDecimal 976 977fun main() { 978 val piObjectJson = """ 979 { 980 "pi_literal": 3.141592653589793238462643383279 981 } 982 """.trimIndent() 983 984 val piObject: JsonObject = Json.decodeFromString(piObjectJson) 985 986 val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content 987 988 val pi = BigDecimal(piJsonLiteral) 989 990 println(pi) 991} 992``` 993 994> You can get the full code [here](../guide/example/example-json-23.kt). 995 996The exact value of `pi` is decoded, with all 30 decimal places of precision that were in the source JSON. 997 998```text 9993.141592653589793238462643383279 1000``` 1001 1002<!--- TEST --> 1003 1004#### Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden 1005 1006To avoid creating an inconsistent state, encoding a String equal to `"null"` is forbidden. 1007Use [JsonNull] or [JsonPrimitive] instead. 1008 1009```kotlin 1010@OptIn(ExperimentalSerializationApi::class) 1011fun main() { 1012 // caution: creating null with JsonUnquotedLiteral will cause an exception! 1013 JsonUnquotedLiteral("null") 1014} 1015``` 1016 1017> You can get the full code [here](../guide/example/example-json-24.kt). 1018 1019```text 1020Exception 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 1021``` 1022 1023<!--- TEST LINES_START --> 1024 1025 1026## Json transformations 1027 1028To affect the shape and contents of JSON output after serialization, or adapt input to deserialization, 1029it is possible to write a [custom serializer](serializers.md). However, it may be inconvenient to 1030carefully follow [Encoder] and [Decoder] calling conventions, especially for relatively small and easy tasks. 1031For that purpose, Kotlin serialization provides an API that can reduce the burden of implementing a custom 1032serializer to a problem of manipulating a Json elements tree. 1033 1034We recommend that you get familiar with the [Serializers](serializers.md) chapter: among other things, it 1035explains how custom serializers are bound to classes. 1036 1037Transformation capabilities are provided by the abstract [JsonTransformingSerializer] class which implements [KSerializer]. 1038Instead of direct interaction with `Encoder` or `Decoder`, this class asks you to supply transformations for JSON tree 1039represented by the [JsonElement] class using the`transformSerialize` and 1040`transformDeserialize` methods. Let's take a look at the examples. 1041 1042### Array wrapping 1043 1044The first example is an implementation of JSON array wrapping for lists. 1045 1046Consider a REST API that returns a JSON array of `User` objects, or a single object (not wrapped into an array) if there 1047is only one element in the result. 1048 1049In the data model, use the [`@Serializable`][Serializable] annotation to specify a custom serializer for a 1050`users: List<User>` property. 1051 1052<!--- INCLUDE 1053import kotlinx.serialization.builtins.* 1054--> 1055 1056```kotlin 1057@Serializable 1058data class Project( 1059 val name: String, 1060 @Serializable(with = UserListSerializer::class) 1061 val users: List<User> 1062) 1063 1064@Serializable 1065data class User(val name: String) 1066``` 1067 1068Since this example covers only the deserialization case, you can implement `UserListSerializer` and override only the 1069`transformDeserialize` function. The `JsonTransformingSerializer` constructor takes an original serializer 1070as parameter (this approach is shown in the section [Constructing collection serializers](serializers.md#constructing-collection-serializers)): 1071 1072```kotlin 1073object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) { 1074 // If response is not an array, then it is a single object that should be wrapped into the array 1075 override fun transformDeserialize(element: JsonElement): JsonElement = 1076 if (element !is JsonArray) JsonArray(listOf(element)) else element 1077} 1078``` 1079 1080Now you can test the code with a JSON array or a single JSON object as inputs. 1081 1082```kotlin 1083fun main() { 1084 println(Json.decodeFromString<Project>(""" 1085 {"name":"kotlinx.serialization","users":{"name":"kotlin"}} 1086 """)) 1087 println(Json.decodeFromString<Project>(""" 1088 {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]} 1089 """)) 1090} 1091``` 1092 1093> You can get the full code [here](../guide/example/example-json-25.kt). 1094 1095The output shows that both cases are correctly deserialized into a Kotlin [List]. 1096 1097```text 1098Project(name=kotlinx.serialization, users=[User(name=kotlin)]) 1099Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)]) 1100``` 1101 1102<!--- TEST --> 1103 1104### Array unwrapping 1105 1106You can also implement the `transformSerialize` function to unwrap a single-element list into a single JSON object 1107during serialization: 1108 1109<!--- INCLUDE 1110import kotlinx.serialization.builtins.* 1111 1112@Serializable 1113data class Project( 1114 val name: String, 1115 @Serializable(with = UserListSerializer::class) 1116 val users: List<User> 1117) 1118 1119@Serializable 1120data class User(val name: String) 1121 1122object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) { 1123--> 1124 1125```kotlin 1126 override fun transformSerialize(element: JsonElement): JsonElement { 1127 require(element is JsonArray) // this serializer is used only with lists 1128 return element.singleOrNull() ?: element 1129 } 1130``` 1131 1132<!--- INCLUDE 1133} 1134--> 1135 1136Now, if you serialize a single-element list of objects from Kotlin: 1137 1138```kotlin 1139fun main() { 1140 val data = Project("kotlinx.serialization", listOf(User("kotlin"))) 1141 println(Json.encodeToString(data)) 1142} 1143``` 1144 1145> You can get the full code [here](../guide/example/example-json-26.kt). 1146 1147You end up with a single JSON object, not an array with one element: 1148 1149```text 1150{"name":"kotlinx.serialization","users":{"name":"kotlin"}} 1151``` 1152 1153<!--- TEST --> 1154 1155### Manipulating default values 1156 1157Another kind of useful transformation is omitting specific values from the output JSON, for example, if it 1158is used as default when missing or for other reasons. 1159 1160Imagine that you cannot specify a default value for the `language` property in the `Project` data model for some reason, 1161but you need it omitted from the JSON when it is equal to `Kotlin` (we can all agree that Kotlin should be default anyway). 1162You can fix it by writing the special `ProjectSerializer` based on 1163the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) for the `Project` class. 1164 1165```kotlin 1166@Serializable 1167class Project(val name: String, val language: String) 1168 1169object ProjectSerializer : JsonTransformingSerializer<Project>(Project.serializer()) { 1170 override fun transformSerialize(element: JsonElement): JsonElement = 1171 // Filter out top-level key value pair with the key "language" and the value "Kotlin" 1172 JsonObject(element.jsonObject.filterNot { 1173 (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin" 1174 }) 1175} 1176``` 1177 1178In the example below, we are serializing the `Project` class at the top-level, so we explicitly 1179pass the above `ProjectSerializer` to [Json.encodeToString] function as was shown in 1180the [Passing a serializer manually](serializers.md#passing-a-serializer-manually) section: 1181 1182```kotlin 1183fun main() { 1184 val data = Project("kotlinx.serialization", "Kotlin") 1185 println(Json.encodeToString(data)) // using plugin-generated serializer 1186 println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer 1187} 1188``` 1189 1190> You can get the full code [here](../guide/example/example-json-27.kt). 1191 1192See the effect of the custom serializer: 1193 1194```text 1195{"name":"kotlinx.serialization","language":"Kotlin"} 1196{"name":"kotlinx.serialization"} 1197``` 1198 1199<!--- TEST --> 1200 1201### Content-based polymorphic deserialization 1202 1203Typically, [polymorphic serialization](polymorphism.md) requires a dedicated `"type"` key 1204(also known as _class discriminator_) in the incoming JSON object to determine the actual serializer 1205which should be used to deserialize Kotlin class. 1206 1207However, sometimes the `type` property may not be present in the input. In this case, you need to guess 1208the actual type by the shape of JSON, for example by the presence of a specific key. 1209 1210[JsonContentPolymorphicSerializer] provides a skeleton implementation for such a strategy. 1211To use it, override its `selectDeserializer` method. 1212Let's start with the following class hierarchy. 1213 1214> Note that is does not have to be `sealed` as recommended in the [Sealed classes](polymorphism.md#sealed-classes) section, 1215> because we are not going to take advantage of the plugin-generated code that automatically selects the 1216> appropriate subclass, but are going to implement this code manually. 1217 1218<!--- INCLUDE 1219import kotlinx.serialization.builtins.* 1220--> 1221 1222```kotlin 1223@Serializable 1224abstract class Project { 1225 abstract val name: String 1226} 1227 1228@Serializable 1229data class BasicProject(override val name: String): Project() 1230 1231 1232@Serializable 1233data class OwnedProject(override val name: String, val owner: String) : Project() 1234``` 1235 1236You can distinguish the `BasicProject` and `OwnedProject` subclasses by the presence of 1237the `owner` key in the JSON object. 1238 1239```kotlin 1240object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) { 1241 override fun selectDeserializer(element: JsonElement) = when { 1242 "owner" in element.jsonObject -> OwnedProject.serializer() 1243 else -> BasicProject.serializer() 1244 } 1245} 1246``` 1247 1248When you use this serializer to serialize data, either [registered](polymorphism.md#registered-subclasses) or 1249the default serializer is selected for the actual type at runtime: 1250 1251```kotlin 1252fun main() { 1253 val data = listOf( 1254 OwnedProject("kotlinx.serialization", "kotlin"), 1255 BasicProject("example") 1256 ) 1257 val string = Json.encodeToString(ListSerializer(ProjectSerializer), data) 1258 println(string) 1259 println(Json.decodeFromString(ListSerializer(ProjectSerializer), string)) 1260} 1261``` 1262 1263> You can get the full code [here](../guide/example/example-json-28.kt). 1264 1265No class discriminator is added in the JSON output: 1266 1267```text 1268[{"name":"kotlinx.serialization","owner":"kotlin"},{"name":"example"}] 1269[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)] 1270``` 1271 1272<!--- TEST --> 1273 1274### Extending the behavior of the plugin generated serializer 1275In some cases, it may be necessary to add additional serialization logic on top of the plugin generated logic. 1276For example, to add a preliminary modification of JSON elements or to add processing of unknown values of enums. 1277 1278In this case, you can mark the serializable class with the [`@KeepGeneratedSerializer`][KeepGeneratedSerializer] annotation and get the generated serializer using the `generatedSerializer()` function. 1279 1280> This annotation is currently experimental. Kotlin 2.0.20 or higher is required for this feature to work. 1281 1282Here is an example of the simultaneous use of [JsonTransformingSerializer] and polymorphism. 1283In this example, we use `transformDeserialize` function to rename `basic-name` key into `name` so it matches the `abstract val name` property from the `Project` supertype. 1284```kotlin 1285@Serializable 1286sealed class Project { 1287 abstract val name: String 1288} 1289 1290@OptIn(ExperimentalSerializationApi::class) 1291@KeepGeneratedSerializer 1292@Serializable(with = BasicProjectSerializer::class) 1293@SerialName("basic") 1294data class BasicProject(override val name: String): Project() 1295 1296object BasicProjectSerializer : JsonTransformingSerializer<BasicProject>(BasicProject.generatedSerializer()) { 1297 override fun transformDeserialize(element: JsonElement): JsonElement { 1298 val jsonObject = element.jsonObject 1299 return if ("basic-name" in jsonObject) { 1300 val nameElement = jsonObject["basic-name"] ?: throw IllegalStateException() 1301 JsonObject(mapOf("name" to nameElement)) 1302 } else { 1303 jsonObject 1304 } 1305 } 1306} 1307 1308 1309fun main() { 1310 val project = Json.decodeFromString<Project>("""{"type":"basic","basic-name":"example"}""") 1311 println(project) 1312} 1313``` 1314 1315> You can get the full code [here](../guide/example/example-json-29.kt). 1316 1317`BasicProject` will be printed to the output: 1318 1319```text 1320BasicProject(name=example) 1321``` 1322<!--- TEST --> 1323 1324### Under the hood (experimental) 1325 1326Although abstract serializers mentioned above can cover most of the cases, it is possible to implement similar machinery 1327manually, using only the [KSerializer] class. 1328If tweaking the abstract methods `transformSerialize`/`transformDeserialize`/`selectDeserializer` is not enough, 1329then altering `serialize`/`deserialize` is a way to go. 1330 1331Here are some useful things about custom serializers with [Json]: 1332 1333* [Encoder] can be cast to [JsonEncoder], and [Decoder] to [JsonDecoder], if the current format is [Json]. 1334* `JsonDecoder` has the [decodeJsonElement][JsonDecoder.decodeJsonElement] method and `JsonEncoder` 1335 has the [encodeJsonElement][JsonEncoder.encodeJsonElement] method, 1336 which basically retrieve an element from and insert an element to a current position in the stream. 1337* Both [`JsonDecoder`][JsonDecoder.json] and [`JsonEncoder`][JsonEncoder.json] have the `json` property, 1338 which returns [Json] instance with all settings that are currently in use. 1339* [Json] has the [encodeToJsonElement][Json.encodeToJsonElement] and [decodeFromJsonElement][Json.decodeFromJsonElement] methods. 1340 1341Given all that, it is possible to implement two-stage conversion `Decoder -> JsonElement -> value` or 1342`value -> JsonElement -> Encoder`. 1343For example, you can implement a fully custom serializer for the following `Response` class so that its 1344`Ok` subclass is represented directly, but the `Error` subclass is represented by an object with the error message: 1345 1346<!--- INCLUDE 1347import kotlinx.serialization.descriptors.* 1348import kotlinx.serialization.encoding.* 1349--> 1350 1351```kotlin 1352@Serializable(with = ResponseSerializer::class) 1353sealed class Response<out T> { 1354 data class Ok<out T>(val data: T) : Response<T>() 1355 data class Error(val message: String) : Response<Nothing>() 1356} 1357 1358class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Response<T>> { 1359 override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Response") { 1360 element("Ok", dataSerializer.descriptor) 1361 element("Error", buildClassSerialDescriptor("Error") { 1362 element<String>("message") 1363 }) 1364 } 1365 1366 override fun deserialize(decoder: Decoder): Response<T> { 1367 // Decoder -> JsonDecoder 1368 require(decoder is JsonDecoder) // this class can be decoded only by Json 1369 // JsonDecoder -> JsonElement 1370 val element = decoder.decodeJsonElement() 1371 // JsonElement -> value 1372 if (element is JsonObject && "error" in element) 1373 return Response.Error(element["error"]!!.jsonPrimitive.content) 1374 return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element)) 1375 } 1376 1377 override fun serialize(encoder: Encoder, value: Response<T>) { 1378 // Encoder -> JsonEncoder 1379 require(encoder is JsonEncoder) // This class can be encoded only by Json 1380 // value -> JsonElement 1381 val element = when (value) { 1382 is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data) 1383 is Response.Error -> buildJsonObject { put("error", value.message) } 1384 } 1385 // JsonElement -> JsonEncoder 1386 encoder.encodeJsonElement(element) 1387 } 1388} 1389``` 1390 1391Having this serializable `Response` implementation, you can take any serializable payload for its data 1392and serialize or deserialize the corresponding responses: 1393 1394```kotlin 1395@Serializable 1396data class Project(val name: String) 1397 1398fun main() { 1399 val responses = listOf( 1400 Response.Ok(Project("kotlinx.serialization")), 1401 Response.Error("Not found") 1402 ) 1403 val string = Json.encodeToString(responses) 1404 println(string) 1405 println(Json.decodeFromString<List<Response<Project>>>(string)) 1406} 1407``` 1408 1409> You can get the full code [here](../guide/example/example-json-30.kt). 1410 1411This gives you fine-grained control on the representation of the `Response` class in the JSON output: 1412 1413```text 1414[{"name":"kotlinx.serialization"},{"error":"Not found"}] 1415[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)] 1416``` 1417 1418<!--- TEST --> 1419 1420### Maintaining custom JSON attributes 1421 1422A good example of custom JSON-specific serializer would be a deserializer 1423that packs all unknown JSON properties into a dedicated field of `JsonObject` type. 1424 1425Let's add `UnknownProject` – a class with the `name` property and arbitrary details flattened into the same object: 1426 1427<!--- INCLUDE 1428import kotlinx.serialization.descriptors.* 1429import kotlinx.serialization.encoding.* 1430--> 1431 1432```kotlin 1433data class UnknownProject(val name: String, val details: JsonObject) 1434``` 1435 1436However, the default plugin-generated serializer requires details 1437to be a separate JSON object and that's not what we want. 1438 1439To mitigate that, write an own serializer that uses the fact that it works only with the `Json` format: 1440 1441```kotlin 1442object UnknownProjectSerializer : KSerializer<UnknownProject> { 1443 override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") { 1444 element<String>("name") 1445 element<JsonElement>("details") 1446 } 1447 1448 override fun deserialize(decoder: Decoder): UnknownProject { 1449 // Cast to JSON-specific interface 1450 val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") 1451 // Read the whole content as JSON 1452 val json = jsonInput.decodeJsonElement().jsonObject 1453 // Extract and remove name property 1454 val name = json.getValue("name").jsonPrimitive.content 1455 val details = json.toMutableMap() 1456 details.remove("name") 1457 return UnknownProject(name, JsonObject(details)) 1458 } 1459 1460 override fun serialize(encoder: Encoder, value: UnknownProject) { 1461 error("Serialization is not supported") 1462 } 1463} 1464``` 1465 1466Now it can be used to read flattened JSON details as `UnknownProject`: 1467 1468```kotlin 1469fun main() { 1470 println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}""")) 1471} 1472``` 1473 1474> You can get the full code [here](../guide/example/example-json-31.kt). 1475 1476```text 1477UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"}) 1478``` 1479 1480<!--- TEST --> 1481 1482--- 1483 1484The next chapter covers [Alternative and custom formats (experimental)](formats.md). 1485 1486 1487<!-- references --> 1488[RFC-4627]: https://www.ietf.org/rfc/rfc4627.txt 1489[BigDecimal]: https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html 1490 1491<!-- stdlib references --> 1492[Double]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/ 1493[Double.NaN]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html 1494[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/ 1495[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/ 1496 1497<!--- MODULE /kotlinx-serialization-core --> 1498<!--- INDEX kotlinx-serialization-core/kotlinx.serialization --> 1499 1500[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html 1501[InheritableSerialInfo]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-inheritable-serial-info/index.html 1502[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html 1503[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html 1504[KeepGeneratedSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-keep-generated-serializer/index.html 1505 1506<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding --> 1507 1508[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html 1509[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html 1510 1511<!--- MODULE /kotlinx-serialization-json --> 1512<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json --> 1513 1514[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html 1515[Json()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html 1516[JsonBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html 1517[JsonBuilder.prettyPrint]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/pretty-print.html 1518[JsonBuilder.isLenient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/is-lenient.html 1519[JsonBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html 1520[JsonNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html 1521[JsonBuilder.useAlternativeNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html 1522[JsonBuilder.encodeDefaults]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html 1523[JsonBuilder.explicitNulls]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html 1524[JsonBuilder.coerceInputValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html 1525[JsonBuilder.allowStructuredMapKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html 1526[JsonBuilder.allowSpecialFloatingPointValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html 1527[JsonBuilder.classDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html 1528[JsonClassDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/index.html 1529[JsonBuilder.classDiscriminatorMode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator-mode.html 1530[ClassDiscriminatorMode.NONE]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-n-o-n-e/index.html 1531[ClassDiscriminatorMode.POLYMORPHIC]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-p-o-l-y-m-o-r-p-h-i-c/index.html 1532[ClassDiscriminatorMode.ALL_JSON_OBJECTS]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-a-l-l_-j-s-o-n_-o-b-j-e-c-t-s/index.html 1533[JsonBuilder.decodeEnumsCaseInsensitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html 1534[JsonBuilder.namingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/naming-strategy.html 1535[JsonNamingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/index.html 1536[JsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/index.html 1537[Json.parseToJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html 1538[JsonPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/index.html 1539[JsonPrimitive.content]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/content.html 1540[JsonPrimitive()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html 1541[JsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-array/index.html 1542[JsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/index.html 1543[_jsonPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-primitive.html 1544[_jsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-array.html 1545[_jsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-object.html 1546[int]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int.html 1547[intOrNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int-or-null.html 1548[long]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long.html 1549[longOrNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long-or-null.html 1550[buildJsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-array.html 1551[buildJsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html 1552[Json.decodeFromJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html 1553[JsonUnquotedLiteral]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html 1554[JsonNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-null/index.html 1555[JsonTransformingSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/index.html 1556[Json.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html 1557[JsonContentPolymorphicSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/index.html 1558[JsonEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/index.html 1559[JsonDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/index.html 1560[JsonDecoder.decodeJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html 1561[JsonEncoder.encodeJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html 1562[JsonDecoder.json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/json.html 1563[JsonEncoder.json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/json.html 1564[Json.encodeToJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html 1565 1566<!--- END --> 1567