Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs-website/labels.list
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE labels SYSTEM "https://resources.jetbrains.com/writerside/1.0/labels-list.dtd">
<labels xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/labels.xsd">

<primary-label id="experimental-general" name="Experimental" short-name="Experimental" href="components-stability.html#stability-levels-explained" color="red">The feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes.</primary-label>
<primary-label id="experimental-opt-in" name="Experimental" short-name="Experimental" href="components-stability.html#stability-levels-explained" color="red" >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.</primary-label>
<primary-label id="alpha" name="Alpha" short-name="α" href="components-stability.html#stability-levels-explained" color="strawberry">The feature is in Alpha. It may change incompatibly and require manual migration in the future.</primary-label>
<primary-label id="beta" name="Beta" short-name="β" href="components-stability.html#stability-levels-explained" color="tangerine">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.</primary-label>
<primary-label id="advanced" name="Advanced" short-name="☆" color="purple"></primary-label>
<primary-label id="eap" name="EAP" short-name="EAP" color="red">This functionality is available only in the latest EAP version.</primary-label>

<secondary-label id="eap" name="EAP" color="red">This functionality is available only in the latest EAP version.</secondary-label>
</labels>
341 changes: 341 additions & 0 deletions docs-website/topics/serialization-json-elements.md
Original file line number Diff line number Diff line change
@@ -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
<primary-label ref="experimental-general"/>

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<Project>(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.