std.data.json — JSON helpers
std.data.json (also exported as std.json) gives you explicit JSON parsing and conversion routines for working with Mux Json values.
data.json.parse(string source) -> result<Json, string>— parses a JSON string.data.json.from_map(map<string, T>) -> result<Json, string>— converts a string-keyed map into aJsonobject (generic over values).data.json.to_map(Json value) -> result<map<string, Json>, string>— extracts an object map if theJsonvalue is an object.
Reading values out of a document
A Json value has typed accessors. Each returns a result<T, string>, and the
error names what was actually there:
| Accessor | Returns |
|---|---|
as_string() | result<string, string> |
as_int() | result<int, string> |
as_float() | result<float, string> |
as_bool() | result<bool, string> |
as_list() | result<list<Json>, string> |
as_map() | result<map<string, Json>, string> |
is_null() | bool |
They return a result rather than an optional because "not an int" is worth
saying why. A bare none leaves you unable to tell a string from a null from
something else - exactly the information you need when a document is not the
shape you expected:
expected an int, found a stringThat matters most in the escape hatch, where you are deliberately reading data whose shape you could not declare. For a document you can describe, prefer parsing straight into a class - the error there names the field as well.
stringify is not an accessor
stringify returns the JSON encoding of a value, which for a string
includes its quotes:
Use stringify when you want JSON text back out. Use an accessor when you want
the value.
Pick the accessor by what the field holds
An accessor reports the wrong kind rather than converting, so a number read with
as_string is err("expected a string, found an int"), not "36". A status
code in a document is a number, so it reads through as_int.
An integral float converts: {"n": 42.0} reads through as_int as 42. A
fractional one does not - 1.5 is an error rather than silently truncating to
1 - and neither does a value outside the range of an int.
Round-trip fidelity
Parsing a document and serializing it again preserves its values and their order.
Integers stay integers. A whole number is not widened to a floating-point
value, so {"id":42} comes back as {"id":42} and not {"id":42.0}. That
matters wherever a receiver is strict about the difference - an HTTP status, a
record id, an array index - and it means values beyond the range a float can
represent exactly keep their value rather than silently rounding to a nearby
one. A number written with a decimal point stays a float.
Key order is preserved. Object keys come back in the order the document
listed them rather than sorted, so a re-serialized document reads the way it was
written and diffs stay meaningful. This is the same guarantee Mux's own map
gives: printed output does not depend on how a container arranged itself
internally.
What is not preserved is source formatting. Insignificant whitespace, the choice between an escape and a literal character, and other spelling details of the original text are normalized by serialization. So a round trip is not byte-for-byte, and re-serialized output is not by itself a canonical form - signing or byte-comparing it requires a canonicalization step these routines do not provide.