Working with JSON
Parse JSON documents, inspect nested values, and serialize data back to text.
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:
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():
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:
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 istrue. A key set tofalsestill evaluates totruein a conditional. UseOr(false)to read a JSON boolean value.
Iterating Collections
Step through object members or array elements using range-for loops with AsObject() or AsArray():
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:
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():
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():
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:
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.