A document in your source stays a document — no parse step, no conversion calls
Most languages make JSON a library: parse a string, get a bag of something, convert every field by hand. In CX+AI it is a type, and a document you wrote in your source stays a document you can read.
One page. Everything here was run.
A _json block is not a string. There is not one escaped quote in it, the compiler parses it when the file is compiled, and a malformed document is an error on its own line — not a bad handle discovered somewhere else at run time.
_json fleet {
{
"port": "Valletta",
"ships": [
{ "name": "Nostromo", "crew": 7, "hull": 0.82 },
{ "name": "Sulaco", "crew": 12, "hull": 1.00 }
]
}
}
printf("out of %s\n", fleet["port"]);
out of Valletta
From the next line on, fleet is an ordinary json handle. A literal inside a loop never re-parses, because there is no text to parse.
Not a query language — a path. It crosses objects and arrays the same way, and the value that comes back is a value: it goes straight into a typed variable, into printf, into arithmetic.
first.s = fleet["ships"][0]["name"]; // "Nostromo"
hull.f = fleet["ships"][1]["hull"]; // 1.0
No conversion step anywhere. The variable's own type is the instruction: .s reads a string, .f reads a double. That is the whole of what atoi, strtod and a %-format you got wrong used to be for.
->count is how many, type is what kind, valid is whether it is still live. It is spelled as a field because that is what it is — an attribute of the thing you are holding, with no function call standing between you and it.
printf("%d fields, %d ships\n", fleet->count, fleet["ships"]->count);
2 fields, 2 ships
Type 5 is an object and type 4 is an array, so fleet->type and fleet["ships"]->type answer 5 and 4.
foreach walks the container; jsonGet hands you the current element and jsonKey its key when you are walking an object.
json ships = fleet["ships"];
json ship;
crew.i = 0;
foreach ships {
ship = jsonGet(ships);
crew = crew + ship["crew"];
printf(" %-10s crew %2d\n", ship["name"], ship["crew"]);
}
printf("%d hands aboard\n", crew);
Nostromo crew 7
Sulaco crew 12
19 hands aboard
Nothing below was declared. manifest is not declared, manifest.hands is not declared, and neither needs to be: the assignment builds the path it is given. No schema, no allocation, no sizes, no cleanup.
fleet["manifest"]["hands"] = crew;
fleet["manifest"]["berths"] = ships->count;
parseDoc is for the shape you did not know when you wrote the program — a request body, a config file, a model's answer. What you did know goes in a _json block above.
string sent = "{ \"port\": 8080, \"host\": \"localhost\", \"ratio\": 0.75 }";
json cfg = parseDoc(sent);
port.i = cfg["port"];
host.s = cfg["host"];
ratio.f = cfg["ratio"];
printf("%s:%d at %.2f\n", host, port, ratio);
localhost:8080 at 0.75
Three reads of one document, three types, no conversion call. jsonExport takes it back the other way.
A missing key reads as nothing, not as an error. cfg["nope"] into an int sink is 0 and into a string sink is empty. If a key's absence matters to you, ask: if (cfg["nope"]->type == 0).
if (doc <= 0) does not mean what it looks like. It compares the document's value, not whether the handle is good. Ask the question you mean:
json doc = parseDoc("{\"a\":1}");
if (doc->valid) { printf("usable\n"); }
usable
A container passed to a function is shared, not copied. function f(json d) gets the caller's document, and what it writes the caller sees. That is the point; there is no byref keyword to remember.
Reassigning one container to another is refused, with a warning, because two owners on one object is a double free waiting to happen. Pass it to a function instead.