skip to content

Show how to construct ad-hoc JSON with the buildJsonObject {} and buildJsonArray {} DSLs. What builder functions are available inside the block?

level: middleimportance: must knowfreq 60%

answer

  1. buildJsonObject { put(...) }, buildJsonArray { add(...) }
  2. Nesting: putJsonObject / putJsonArray; addJsonObject / addJsonArray
  3. put overloads: String/Int/Long/Double/Boolean/JsonElement
  4. Result is immutable JsonObject/JsonArray
  5. Same type-safe-builder pattern as buildList

basics

~10 s

buildJsonObject { } lets you assemble a JSON object by calling put("key", value) for primitives, and putJsonObject/putJsonArray for nested structures. buildJsonArray { } builds arrays with add(...). They return immutable JsonObject/JsonArray.

solid answer

~30 s

`buildJsonObject { }` and `buildJsonArray { }` are DSL builders (in `kotlinx.serialization.json`) for creating JSON trees programmatically without a backing class. Inside `buildJsonObject` the receiver is a `JsonObjectBuilder`, exposing `put(key, String/Int/Long/Double/Boolean/JsonElement)`, the nesting helpers `putJsonObject(key) { }` and `putJsonArray(key) { }`, and `putJsonObject`/raw `put` for a pre-built element. Inside `buildJsonArray` the receiver is a `JsonArrayBuilder` with `add(...)`, `addJsonObject { }`, `addJsonArray { }`, and `addAll(...)`. Both builders return **immutable** `JsonObject`/`JsonArray`. This is ideal for shaping request bodies, test fixtures, or merging dynamic fields. You can then serialise with `Json.encodeToString(JsonElement.serializer(), tree)` or `tree.toString()`.

code

kotlin · 7 lines
kotlin
import kotlinx.serialization.json.*

val tree = buildJsonArray {
    addJsonObject { put("id", 1); put("name", "a") }
    addJsonObject { put("id", 2); put("name", "b") }
}
println(tree)   // [{"id":1,"name":"a"},{"id":2,"name":"b"}]

go deeper

for a junior

Can call put for primitives and produce a flat object.

for a middle

Uses putJsonObject/putJsonArray and add* for nesting and knows the result is immutable.

for a senior

Explains when to hand-build vs encodeToJsonElement, and how to rebuild for edits cleanly.

for a principal

Weighs DSL ergonomics vs typed models at API boundaries and standardises a pattern for dynamic payload assembly across a codebase.

## The two builder DSLs kotlinx.serialization ships two top-level builder functions in `kotlinx.serialization.json`: - `buildJsonObject { }` → returns an immutable `JsonObject` - `buildJsonArray { }` → returns an immutable `JsonArray` They are **type-safe builders** (the same Kotlin DSL pattern used by `buildList`/`buildString`): the lambda has a *receiver* object whose members you call to add entries. ## Inside buildJsonObject — the JsonObjectBuilder receiver - **`put(key, value)`** — overloads accept `String`, `Number` (`Int`, `Long`, `Double`), `Boolean`, and `JsonElement`. There's also `put(key, null as String?)` style and a `JsonNull` overload. - **`putJsonObject(key) { ... }`** — nests another object built by an inner block. - **`putJsonArray(key) { ... }`** — nests an array. ## Inside buildJsonArray — the JsonArrayBuilder receiver - **`add(value)`** — `String`/`Number`/`Boolean`/`JsonElement` overloads. - **`addJsonObject { ... }`** and **`addJsonArray { ... }`** — nested structures. - **`addAll(collection)`** — bulk add. ## Example ```kotlin import kotlinx.serialization.json.* val payload = buildJsonObject { put("name", "Ada") put("age", 36) put("active", true) putJsonArray("langs") { add("Kotlin") add("Java") } putJsonObject("address") { put("city", "London") } } // {"name":"Ada","age":36,"active":true,"langs":["Kotlin","Java"],"address":{"city":"London"}} val text = payload.toString() // or Json.encodeToString(payload) ``` ## Immutability The returned `JsonObject`/`JsonArray` are **read-only** snapshots. To "modify" one you build a new tree (e.g. `buildJsonObject { original.forEach { (k, v) -> put(k, v) }; put("extra", 1) }`). There is no in-place mutation. ## When to use it Great for request bodies whose shape is dynamic, test fixtures, or assembling responses field-by-field. For a stable schema, prefer `encodeToJsonElement(value)` on a typed object instead of hand-building.

  • Can you mutate a JsonObject after building it?
    No. It's an immutable read-only snapshot (a Map view). You rebuild a new tree to add or change fields.
  • How do you add an already-built JsonElement under a key?
    Use the put(key, element) overload that accepts a JsonElement directly inside buildJsonObject.

saying these in an interview costs you the question

  • Trying to mutate the result with obj["k"] = v (it's read-only)
  • Inventing methods like set() or addObject() instead of put/add/putJsonObject
  • Thinking you must wrap primitives manually as JsonPrimitive for every put (overloads exist)
  • Confusing buildJsonObject (returns JsonObject) with Json {} (the configuration builder)

context