skip to content

Given an unknown JSON string, how do you parse it with parseToJsonElement and safely navigate optional/nested fields? Contrast the throwing accessors with the *OrNull variants.

level: middleimportance: should knowfreq 55%

answer

  1. parseToJsonElement → tree; malformed throws SerializationException
  2. .jsonObject/.jsonPrimitive THROW on mismatch
  3. *OrNull readers (intOrNull, contentOrNull) return null instead
  4. obj["k"] is nullable (absent key) vs JsonNull (explicit null)
  5. Safe path = ?. + as? + *OrNull

basics

~20 s

Call Json.parseToJsonElement(text) to get a tree, then drill in with obj["key"]. For safety use the ...OrNull helpers (jsonObject vs the safe path, intOrNull, contentOrNull) plus Kotlin's ?. so a missing or wrong-typed field gives null instead of throwing.

solid answer

~30 s

`Json.parseToJsonElement(string)` returns a `JsonElement` for arbitrary input. To navigate, index objects with `obj["key"]` (returns nullable `JsonElement?` because the key may be absent) and arrays with `arr[i]`. The accessor properties `.jsonObject`, `.jsonArray`, `.jsonPrimitive` **throw** `IllegalArgumentException` on a type mismatch — fine when you're certain of the shape. For untrusted/dynamic input, combine the nullable index with `?.` and the *OrNull readers: `.jsonPrimitive` still throws, so guard with `(el as? JsonPrimitive)` or rely on `intOrNull`/`longOrNull`/`booleanOrNull`/`contentOrNull` to turn bad content into `null`. A typical safe read: `tree.jsonObject["user"]?.jsonObject?.get("age")?.jsonPrimitive?.intOrNull`. Wrap the initial `parseToJsonElement` in try/catch for malformed JSON, which throws `SerializationException`.

code

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

fun ageOf(raw: String): Int? = try {
    (Json.parseToJsonElement(raw) as? JsonObject)
        ?.get("age")
        ?.let { it as? JsonPrimitive }
        ?.intOrNull
} catch (e: kotlinx.serialization.SerializationException) { null }

go deeper

for a junior

Can parse and read one known field with obj["k"]!!.jsonPrimitive.content.

for a middle

Distinguishes throwing accessors from *OrNull and builds a safe nested path with ?. and as?.

for a senior

Handles SerializationException, absent-vs-JsonNull, and reasons about which family fits trusted vs untrusted input.

for a principal

Defines a defensive parsing convention/utility so dynamic-JSON reads fail safe and consistently across services.

## Parsing ```kotlin import kotlinx.serialization.json.* val tree: JsonElement = Json.parseToJsonElement(rawString) ``` `parseToJsonElement` reads any valid JSON into a tree. **Malformed** JSON throws `SerializationException` (specifically a `JsonDecodingException`), so wrap it in `try/catch` if the input is untrusted. ## Two families of navigation helpers **Throwing casts** — assume the shape: - `.jsonObject` → `JsonObject` or throws `IllegalArgumentException` - `.jsonArray` → `JsonArray` or throws - `.jsonPrimitive` → `JsonPrimitive` or throws - On a primitive: `.int`, `.long`, `.double`, `.boolean`, `.content` (these throw or return raw) **Null-returning readers** — tolerate surprises: - `.intOrNull`, `.longOrNull`, `.doubleOrNull`, `.booleanOrNull`, `.contentOrNull` - For the container casts there is no `jsonObjectOrNull`, so use Kotlin's safe cast `as?`: `(el as? JsonObject)`. ## Key fact: indexing returns nullable `JsonObject` is a `Map`, so `obj["key"]` returns `JsonElement?` — `null` when the key is **absent**. That is different from the key being present with value `JsonNull`. ## Safe navigation pattern ```kotlin val age: Int? = tree.jsonObject["user"] // JsonElement? (may be null/absent) ?.let { it as? JsonObject } // safe-cast, null if not an object ?.get("age") // JsonElement? again ?.jsonPrimitive // still throws if not a primitive... ?.intOrNull // ...intOrNull tolerates bad content ``` To be fully non-throwing, replace `?.jsonPrimitive` with `?.let { it as? JsonPrimitive }`. Combining `?.` (safe call), `as?` (safe cast), and the `*OrNull` readers gives a path where any wrong assumption collapses to `null` rather than an exception. ## Distinguishing absent vs null ```kotlin when (val v = obj["middleName"]) { null -> "key absent" JsonNull -> "explicitly null" else -> v.jsonPrimitive.content } ``` ## Summary Use throwing accessors when the schema is guaranteed; use nullable indexing + `as?` + `*OrNull` when it isn't. Always guard the initial parse against `SerializationException`.

  • What does obj["missing"] return for an absent key?
    Kotlin null (the Map returns null). That differs from JsonNull, which is a present element holding JSON null.
  • Does .jsonPrimitive on a JsonObject throw?
    Yes, it throws IllegalArgumentException. Use as? JsonPrimitive to avoid throwing.

saying these in an interview costs you the question

  • Believing .jsonObject returns null on mismatch (it throws)
  • Not handling SerializationException from malformed input
  • Conflating an absent key with JsonNull
  • Using .int (throws) where .intOrNull is needed for untrusted data

context