UXJSON
UXJSON parses JSON text into a tree of
UXJSONValue and writes it back out.
Use it to read and write settings, config and simple data interchange. It
pairs with UXKeyValueStore, which
holds the same data without the file.
#use <UXKit> // or #import "UXJSON.xc"Overview
Section titled “Overview”UXJSONValue* doc = UXJSON.parse(text);if (doc == (UXJSONValue*)0) { /* malformed */ }
doc.get((u8*)"title").asString(); // "Rocks"doc.get((u8*)"zoom").asInt(); // 150doc.get((u8*)"grid").asBool(); // true
UXJSONValue* origin = doc.get((u8*)"origin");origin.at(0).asInt(); // 10
u8* out = UXJSON.serialize(doc);The parser is recursive descent; the serialiser makes two passes (measure, then fill). Both are static. The instance fields are the parser’s own cursor and are not part of the interface.
Failure is null, not a partial tree
Section titled “Failure is null, not a partial tree”UXJSON.parse((u8*)"{\"a\":"); // 0A malformed document returns null, not the part parsed before the error. A half-read config is more dangerous than no config, because it looks as if it loaded.
There is no error position. If you need to tell the user where the file is broken, use a different parser. To decide whether a file is usable, one null check is enough.
Strings round-trip exactly
Section titled “Strings round-trip exactly”Text that contains JSON’s own delimiters is the case most likely to go untested:
UXJSONValue* v = UXJSON.parse((u8*)"{\"say\":\"he said \\\"hi\\\"\"}");v.get((u8*)"say").asString(); // he said "hi" — unescaped in the treeUXJSON.serialize(v); // {"say":"he said \"hi\""} — escaped againValues in the tree are plain text, with the escapes removed. serialize
puts them back, so parse → serialize → parse is a fixed point and the output is
always valid JSON, quotes and backslashes included.
Object keys are escaped the same way, so a key containing a quote emits and re-reads correctly.
The escapes handled in both directions are \" \\ \/ \n \t \r \b
\f.
Numbers are integers
Section titled “Numbers are integers”UXJSON.parse((u8*)"{\"a\":3.7,\"b\":-3.7}");// a.asInt() == 3, b.asInt() == -3 — truncated toward zeroUXJSON.serialize(...); // {"a":3,"b":-3}A fractional part is parsed and discarded, not rejected. A document with decimals loads and loses its decimals. The round trip is lossy for those values and exact for everything else.
The i32 model covers sizes, counts, coordinates, flags and enumerations,
which is what a UI toolkit’s config contains.
Topics
Section titled “Topics”parse · serialize · streq · slen · dup
static UXJSONValue* parse(u8* s)Text to a tree, or null if the document is malformed. The input is copied
into the tree, so s can be freed afterwards.
serialize
Section titled “serialize”static u8* serialize(UXJSONValue* v)A tree to text, in a fresh buffer. The output is compact, with no whitespace and no indentation, which suits a config file written by a program.
Two passes: measure computes the length including escapes, then fill
writes it. The two must agree, so the gate asserts round trips over
text full of delimiters.
A null value serialises as null rather than crashing, so a partially built
tree still writes.
static bool streq(u8* a, u8* b)Content comparison, used for key lookup. It is public because code that walks the value tree usually needs it too.
static i32 slen(u8* s)Byte length, null-safe.
static u8* dup(u8* s, i32 start, i32 len)A fresh copy of a range.
Example
Section titled “Example”title=Rocks zoom=150 grid=1origin count=2 [10,20]has(title)=1 has(nope)=0 get(nope)=0re-serialised: {"title":"Rocks","zoom":150,"grid":true,"origin":[10,20],"font":null}awkward: {"say":"he said \"hi\"","path":"C:\\Users","two":"a\nb"}stable: {"say":"he said \"hi\"","path":"C:\\Users","two":"a\nb"}say is really: [he said "hi"]3.7 -> 3 -3.7 -> -3 reserialised: {"a":3,"b":-3}bad input: 0 exponent: 0The program is website/site/examples/uxkit/data.xc. The doc-examples gate
compiles it, and the listing above is its output.
run_json_csv.sh asserts the escaping in both directions, the fixed-point round
trip, escaped keys, and each documented limit above, so the docs stay in step
with the code. It runs with MallocScribble=1, because a two-pass serializer
that under-measures its buffer can otherwise pass by luck.
Conforms to
Section titled “Conforms to”- A plain class (not an
Objectsubclass)
See also
Section titled “See also”UXJSONValue: a node in the treeUXCSV: the tabular counterpartUXKeyValueStore: settings without a file format