Standard Library
namespace json
Parse JSON text into JsonValues and render values back to text.
since 0.1.0-alpha.1linuxwindowswasm
Overview
json::parse never throws: malformed text gives None, so the result must be checked before use. Navigating the parsed value with at is strict and throws on a wrong path, while asStr, asNum and the other as... methods give None on a kind mismatch.
Description
The json namespace turns JSON text into a tree of JsonValue objects and back. It is part of the
standard library; there is nothing to import.
json::parse(text) returns a JsonValue?. It is total: a malformed document, trailing text after
the value, an empty string and over-deep nesting all produce None instead of throwing, so a program
that reads untrusted input only has to test for None. json::render(value) is the inverse and is the
same as calling value.render().
The parser is a strict recursive-descent parser:
- It accepts the full JSON string escape set, including
\uXXXXescapes. A high and a low surrogate written as two escapes combine into one character; an unpaired surrogate becomes U+FFFD. - Numbers are IEEE doubles. They are held as
float, and they render in the text form offloat.toString(), so36comes back out as36.000000. - Arrays and objects may nest to a depth of 129. Anything deeper parses to
None. - Anything after the closing token of the value, other than white space, makes the whole parse
None. - Object keys render in the order they were first inserted, not sorted.
Parse, navigate and render
string text = "{\"user\": {\"name\": \"Ada\", \"age\": 36, \"tags\": [\"math\", \"code\"]}, \"active\": true}";
JsonValue? doc = json::parse(text);
if (doc != None) {
console.writeln(doc.at("user").at("name").asStr() ?? "?");
console.writeln((doc.at("user").at("age").asNum() ?? 0.0).toString());
console.writeln(doc.at("user").at("tags").at(1).asStr() ?? "?");
console.writeln(doc.render());
}
console.writeln((json::parse("{\"a\": ") == None).toString());
console.writeln((json::parse("[1, 2] trailing") == None).toString());
Ada
36.000000
code
{"user":{"name":"Ada","age":36.000000,"tags":["math","code"]},"active":true}
true
true
Rules
json::parsenever throws. It returnsNonefor malformed input.- The result of a successful parse is a
JsonValuewhose accessors are described on theJsonValuepage:atis loud and throws, the typedas…accessors returnNoneon a mismatch. json::render(v)equalsv.render(): compact text with no white space between tokens.json::renderStr(text)returnstextas a quoted JSON string literal. It escapes"and\, the short escapes\b \f \n \r \t, and every other control byte below 32 as\u00XX. Non-ASCII text passes through unchanged as UTF-8.
Examples
Build a value by hand and render it, compact and indented:
Build, escape and pretty-print
Map<string, JsonValue> m = Map();
m["name"] = JsonValue::ofStr("a \"quoted\"\nline");
m["scores"] = JsonValue::ofArray([JsonValue::ofNum(1.5), JsonValue::ofBool(true), JsonValue::ofNull()]);
JsonValue v = JsonValue::ofObject(m);
console.writeln(json::render(v));
console.writeln(v.renderPretty(2));
console.writeln(json::renderStr("tab\there"));
{"name":"a \"quoted\"\nline","scores":[1.500000,true,null]}
{
"name": "a \"quoted\"\nline",
"scores": [
1.500000,
true,
null
]
}
"tab\there"
Escapes and surrogate pairs:
Unicode escapes
JsonValue? p = json::parse("\"\\u00e9\\ud83d\\ude00\"");
JsonValue v = p ?? JsonValue::ofNull();
console.writeln(v.asStr() ?? "none");
JsonValue? lone = json::parse("\"\\ud83d\"");
JsonValue w = lone ?? JsonValue::ofNull();
console.writeln((w.asStr() ?? "none").length().toString());
é😀
3
Notes
The next example shows how deep nesting is limited. A document nested 129 levels deep parses; one more level does not.
The depth limit
for (int n in [100, 129, 130]) {
string t = "[".repeat(n) + "]".repeat(n);
console.writeln("${n} nested: ${(json::parse(t) == None).toString()}");
}
100 nested: false
129 nested: false
130 nested: true
The printed value is whether parse returned None.
Examples
Parsing, handling bad input, and rendering
JsonValue? good = json::parse("{\"x\": [1, 2]}");
console.writeln(good != None ? json::render(good) : "invalid");
JsonValue? bad = json::parse("{\"x\": [1, 2");
console.writeln(bad != None ? "parsed" : "invalid");
console.writeln(json::renderStr("a\tb"));
{"x":[1.000000,2.000000]}
invalid
"a\tb"
Functions
parse
parse(string s) -> JsonValue | NoneParse JSON text.
The text must be exactly one JSON value, optionally surrounded by whitespace. The parser follows the JSON grammar strictly, with these specifics:
- Any malformed input gives
Nonerather than an exception: unbalanced brackets, trailing commas, single-quoted strings, leading zeros such as01, unknown escapes, an unterminated string, extra text after the value, and empty input. - Numbers are read as floats, with optional fraction and exponent.
- Strings support every JSON escape, including
\uXXXX. A surrogate pair is combined into one character, and an unpaired surrogate becomes U+FFFD. - Nesting is limited to 129 levels; deeper documents give
None.
Parameters
- s
- The JSON text.
Returns
The parsed value, or None when s is not valid JSON.
Examples
void show(string s) {
JsonValue? v = json::parse(s);
console.writeln(v != None ? "ok " + v.render() : "invalid: <" + s + ">");
}
show("{\"a\": 1, \"b\": [true, null, \"x\"]}");
show(" [ ] ");
show("[1, 2,");
show("[1,]");
show("{\"a\": 1} extra");
show("\"caf\\u00e9\"");
show("");
ok {"a":1.000000,"b":[true,null,"x"]}
ok []
invalid: <[1, 2,>
invalid: <[1,]>
invalid: <{"a": 1} extra>
ok "café"
invalid: <>
render
render(JsonValue v) -> stringRender a value as compact JSON text.
This is the same as calling render() on the value.
Parameters
- v
- The value to render.
Returns
The JSON text.
Examples
JsonValue v = json::parse("{ \"a\" : [ 1 , 2 ] }") ?? JsonValue::ofNull();
console.writeln(json::render(v));
{"a":[1.000000,2.000000]}
See also: render
renderStr
renderStr(string v) -> stringQuote and escape a string as a JSON string literal.
The result includes the surrounding double quotes. " and \ are escaped, as are the control characters (short forms such as \n and \t where JSON has them, \u00XX otherwise). Other text, including non-ASCII characters, is copied unchanged.
Parameters
- v
- The text to quote.
Returns
The JSON string literal.
Examples
console.writeln(json::renderStr("plain"));
console.writeln(json::renderStr("say \"hi\"\n"));
console.writeln(json::renderStr("back\\slash"));
console.writeln(json::renderStr("café"));
"plain"
"say \"hi\"\n"
"back\\slash"
"café"
See also: render