Ultralight uses the `JSON` type to represent parsed JSON documents. It lets you inspect nested values, build or modify data, and serialize documents back to text— operations never throw exceptions.

## Creating Documents

### Parsing Text

Pass a string to `JSON::Parse()` to parse a document:

```cpp
JSON config = JSON::Parse(text);
if (!config)
  printf("line %u: %s\n", config.error_line(),
         config.error_message().utf8().data());
```

Testing a `JSON` object in a conditional evaluates to `true` when parsing succeeds and `false` when it fails.

When parsing fails, `error_line()` returns the line number of the syntax error, and `error_message()` describes the problem.

### Empty Documents

Construct a new empty document using `JSON::Object()` or `JSON::Array()`:

```cpp
JSON save = JSON::Object();
JSON scores = JSON::Array();
```

Both methods return empty root documents ready for nested values.

## Reading Values

### Chained Lookups

Chain `operator[]` across keys or indices and call `Or()` to read a value with a fallback:

```cpp
void ApplyConfig(const JSON& config) {
  double scale = config["ui"]["scale"].Or(1.0);  // 1.0 if missing
  String name = config["profile"]["name"].Or("anonymous");
  bool muted = config["audio"]["muted"].Or(false);
}
```

You can chain lookups across nested objects and arrays without checking each step. If a key is missing or an index is out of bounds, the chain safely resolves to the fallback passed to `Or()`.

The `Or()` method does not convert types— reading a string `"3"` with `Or(0)` returns the fallback `0`.

Always read values through a `const` reference. Chaining `operator[]` on a non-const document creates missing keys automatically.

> 🚧 Testing Boolean Keys
>
> Testing a lookup like `if (config["enabled"])` checks whether the key exists, not whether its value is `true`. A key set to `false` still evaluates to `true` in a conditional. Use `Or(false)` to read a JSON boolean value.

### Iterating Collections

Step through object members or array elements using range-for loops with `AsObject()` or `AsArray()`:

```cpp
for (auto [key, value] : config["servers"].AsObject())
  Connect(key, value.Or(""));
```

Object iteration visits keys in the order they appear in the source text.

Calling `AsObject()` or `AsArray()` on the wrong type or a missing key produces an empty loop rather than an error.

## Writing Values

### Setting Values

Assign values to nested keys using `operator[]` on a non-const document:

```cpp
save["player"]["name"] = "ada";  // creates "player" first
save["player"]["level"] = 12;
```

Assigning through nested keys on a non-const document creates missing objects along the path automatically.

### Modifying Arrays

Update array elements by index or append new items with `Push()`:

```cpp
save["flags"] = JSON::Array();
save["flags"][0] = "tutorial_done";

JSON flags = save["flags"];
flags.AsArray().Push("first_boss");
```

Assigning to an index updates that element directly, while `AsArray()` provides access to array methods like `Push()`.

## Serializing to Text

Convert a document back to text using `Stringify()`:

```cpp
String compact = save.Stringify();
String pretty = save.Stringify(2);  // two spaces per level
```

Calling `Stringify()` without arguments formats the document as a compact, single-line string.

Passing an indentation size formats the text across multiple lines with that number of spaces per level.

## Copying Documents

Duplicate a document with `Clone()` or assign it to share the underlying data:

```cpp
JSON shared = save;        // the same document
JSON copy = save.Clone();  // an independent copy
```

Copying a `JSON` instance shares the underlying document— a modification made through one handle is visible across every copy.

Call `Clone()` when you need an independent, deep copy.
