diff --git a/docs-website/labels.list b/docs-website/labels.list new file mode 100644 index 000000000..0d879d6ea --- /dev/null +++ b/docs-website/labels.list @@ -0,0 +1,14 @@ + + + + + The feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. + The feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see the details below), and you should use it only for evaluation purposes. + The feature is in Alpha. It may change incompatibly and require manual migration in the future. + The feature is in Beta. It is almost stable, but migration steps may be required in the future. We'll do our best to minimize any changes you have to make. + + This functionality is available only in the latest EAP version. + + This functionality is available only in the latest EAP version. + \ No newline at end of file diff --git a/docs-website/topics/serialization-json-elements.md b/docs-website/topics/serialization-json-elements.md new file mode 100644 index 000000000..58de5a561 --- /dev/null +++ b/docs-website/topics/serialization-json-elements.md @@ -0,0 +1,341 @@ +[//]: # (title: JSON elements) + +Besides converting between JSON strings and Kotlin objects, the Kotlin serialization library also supports working with JSON at a structural level. +To do this, you can use the [`JsonElement`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/) API, which lets you inspect, modify, and construct JSON structure directly before converting it into a Kotlin type. + +`JsonElement` has three direct subtypes that represent the core JSON structures: + +* [`JsonPrimitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/) handles primitive JSON elements such as strings, numbers, booleans, and `null`. + Each `JsonPrimitive` stores a string representation of its value, which you can access through its [`JsonPrimitive.content`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/content.html) property. +* [`JsonArray`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-array/) is a JSON array. It's a Kotlin [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/) of `JsonElement` items. +* [`JsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/) is a JSON object. It's a Kotlin [`Map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/#kotlin.collections.Map) with `String` keys and `JsonElement` values. + +> You can create a `JsonPrimitive` by passing primitive Kotlin types to the [`JsonPrimitive()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html) function. +> +{style="note"} + +## Parse to JSON elements + +You can parse a string into a `JsonElement` to work with the JSON structure before converting it into a Kotlin type. +To do so, use the [`Json.parseToJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html) function. +This function parses the input into a JSON element tree without decoding or deserializing it. + +Here's an example: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +fun main() { + val element = Json.parseToJsonElement(""" + {"name":"kotlinx.serialization","language":"Kotlin"} + """) + // Prints the JsonElement as a valid JSON string + println(element) + // {"name":"kotlinx.serialization","language":"Kotlin"} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +## Access JSON element contents + +You can access the contents of a JSON element directly through the extension properties of the `JsonElement` API. +These extension properties cast the element to a specific subtype, +and throw an `IllegalArgumentException` if the element doesn't have the expected JSON structure. + +The available extension properties are: + +* [`jsonPrimitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-primitive.html) returns a `JsonPrimitive`. +* [`jsonArray`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-array.html) returns a `JsonArray`. +* [`jsonObject`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-object.html) returns a `JsonObject`. + +Similarly, [`JsonPrimitive`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/) has extension properties for parsing the value as Kotlin primitive types, such as +[`int`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int.html), [`intOrNull`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int-or-null.html), [`long`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long.html), [`longOrNull`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long-or-null.html), and so on. + +Here's an example of how you can use these extension properties when processing JSON data: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +fun main() { + val element = Json.parseToJsonElement(""" + { + "name": "kotlinx.serialization", + "forks": [{"votes": 42}, {"votes": 9000}, {}] + } + """) + val sum = element + // Accesses the forks key from the JsonObject + .jsonObject["forks"]!! + + // Accesses the value as a JsonArray and sums the votes values from each JsonObject as Int + .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 } + println(sum) + // 9042 +} +//sampleEnd +``` +{kotlin-runnable="true"} + +## Create JSON elements + +You can create instances of specific `JsonElement` subtypes directly. + +To create a `JsonPrimitive`, use the `JsonPrimitive()` function: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +fun main() { + // Creates JsonPrimitive values from different Kotlin primitives + val number = JsonPrimitive(42) + val text = JsonPrimitive("kotlinx.serialization") + + println(number) + // 42 + println(text) + // "kotlinx.serialization" +} +//sampleEnd +``` +{kotlin-runnable="true"} + +To create `JsonArray` or `JsonObject` elements, use the +[`buildJsonArray()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-array.html) and [`buildJsonObject()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html) builder functions respectively. +These provide a DSL to define the JSON structure, similar to [Kotlin's standard library collection builders](constructing-collections.md#create-with-collection-builder-functions), but with JSON-specific overloads and inner builder functions. + +Let's look at an example that highlights the key features: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +fun main() { + val element = buildJsonObject { + // Adds a simple key-value pair to the JsonObject + put("name", "kotlinx.serialization") + // Adds a nested JsonObject under the owner key + putJsonObject("owner") { + put("name", "kotlin") + } + // Adds a JsonArray with multiple JsonObjects + putJsonArray("forks") { + // Adds a JsonObject to the JsonArray + addJsonObject { + put("votes", 42) + } + addJsonObject { + put("votes", 9000) + } + } + } + // Prints the resulting JSON string + println(element) + // {"name":"kotlinx.serialization","owner":{"name":"kotlin"},"forks":[{"votes":42},{"votes":9000}]} +} +//sampleEnd +``` +{kotlin-runnable="true"} + +### Encode literal JSON content + + +While the JSON specification doesn't restrict the size or precision of numbers, serializing numbers of arbitrary size with the `JsonPrimitive()` function might lead to some issues. + +For example, if you use [`Double`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/) for large numbers, the value might get truncated and you lose precision. +If you use Kotlin/JVM [`BigDecimal`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/), the value stays precise, but `JsonPrimitive()` encodes the value as a string rather than as a number: + +```kotlin +// Imports necessary declarations from libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.math.BigDecimal + +//sampleStart +val format = Json { prettyPrint = true } + +fun main() { + val pi = BigDecimal("3.141592653589793238462643383279") + + // Converts the BigDecimal to a Double, causing potential truncation + val piJsonDouble = JsonPrimitive(pi.toDouble()) + // Converts the BigDecimal to a String, preserving the precision but treating it as a string in JSON + val piJsonString = JsonPrimitive(pi.toString()) + + val piObject = buildJsonObject { + put("pi_double", piJsonDouble) + put("pi_string", piJsonString) + } + + println(format.encodeToString(piObject)) + // "pi_double": 3.141592653589793, + // "pi_string": "3.141592653589793238462643383279" +} +//sampleEnd +``` +{kotlin-runnable="true"} + +In this example, even though `pi` is defined as a number with 30 decimal places, the resulting JSON doesn't reflect this. +The `Double` value is truncated to 15 decimal places, and the `String` is wrapped in quotes, making it a string instead of a JSON number. + +> The [`JsonUnquotedLiteral()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html) function is [Experimental](components-stability.md#stability-levels-explained). To opt in, use the `@OptIn(ExperimentalSerializationApi::class)` annotation or the compiler option `-opt-in=kotlinx.serialization.ExperimentalSerializationApi`. +> +{style="warning"} + +To avoid these issues, you can encode an arbitrary unquoted value, such as the string value of `pi` in this example, using the [`JsonUnquotedLiteral()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html) function: + +```kotlin +// Imports necessary declarations from libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.math.BigDecimal + +//sampleStart +val format = Json { prettyPrint = true } + +fun main() { + val pi = BigDecimal("3.141592653589793238462643383279") + + // Encodes the raw JSON content using JsonUnquotedLiteral + @OptIn(ExperimentalSerializationApi::class) + val piJsonLiteral = JsonUnquotedLiteral(pi.toString()) + + // Converts to Double and String + val piJsonDouble = JsonPrimitive(pi.toDouble()) + val piJsonString = JsonPrimitive(pi.toString()) + + val piObject = buildJsonObject { + put("pi_literal", piJsonLiteral) + put("pi_double", piJsonDouble) + put("pi_string", piJsonString) + } + + // pi_literal now accurately matches the value defined. + println(format.encodeToString(piObject)) + // "pi_literal": 3.141592653589793238462643383279, + // "pi_double": 3.141592653589793, + // "pi_string": "3.141592653589793238462643383279" +} +//sampleEnd +``` +{kotlin-runnable="true"} + +To decode `pi` back to a `BigDecimal`, extract the string content of the `JsonPrimitive`: + +```kotlin +// Imports necessary declarations from libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.math.BigDecimal + +//sampleStart +fun main() { + val piObjectJson = """ + { + "pi_literal": 3.141592653589793238462643383279 + } + """.trimIndent() + + // Decodes the JSON string into a JsonObject + val piObject: JsonObject = Json.decodeFromString(piObjectJson) + + // Extracts the string content from the JsonPrimitive + val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content + + // Converts the string to a BigDecimal + val pi = BigDecimal(piJsonLiteral) + // Prints the decoded value of pi, preserving all 30 decimal places + println(pi) + // 3.141592653589793238462643383279 +} +//sampleEnd +``` +{kotlin-runnable="true"} + +> This example uses a `JsonPrimitive` for simplicity. For more reusable approaches, see +> [Json Transformations](serialization-transform-json.md). +> +{style="tip"} + +#### JSON null literal + +To avoid creating an inconsistent state, you can't encode the string `"null"` with `JsonUnquotedLiteral`. +Attempting to do so results in an exception: + +```kotlin +// Imports the necessary libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@OptIn(ExperimentalSerializationApi::class) +fun main() { + JsonUnquotedLiteral("null") + // Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException +} +//sampleEnd +``` +{kotlin-runnable="true" validate="false"} + +To represent a JSON `null` literal value, use [`JsonNull`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-null/) instead: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +fun main() { + val possiblyNull = JsonNull + + println(possiblyNull) + // null +} +//sampleEnd +``` +{kotlin-runnable="true"} + +## Decode Json elements + +To decode an instance of the `JsonElement` class into a serializable object, use +the [`Json.decodeFromJsonElement()`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html) function: + +```kotlin +// Imports declarations from the serialization and JSON handling libraries +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +//sampleStart +@Serializable +data class Project(val name: String, val language: String) + +fun main() { + val element = buildJsonObject { + put("name", "kotlinx.serialization") + put("language", "Kotlin") + } + + // Decodes the JsonElement into a Project object + val data = Json.decodeFromJsonElement(element) + println(data) + // Project(name=kotlinx.serialization, language=Kotlin) +} +//sampleEnd +``` +{kotlin-runnable="true"} + +## What's next + +* Discover how to [transform JSON during serialization and deserialization](serialization-transform-json.md) for more control over your data. +* Learn how to [serialize classes](serialization-customization-options.md) and how to modify the default behavior of the `@Serializable` annotation.