LEVIATHAN v962456e · 962456eee1

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 \uXXXX escapes. 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 of float.toString(), so 36 comes back out as 36.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::parse never throws. It returns None for malformed input.
  • The result of a successful parse is a JsonValue whose accessors are described on the JsonValue page: at is loud and throws, the typed as… accessors return None on a mismatch.
  • json::render(v) equals v.render(): compact text with no white space between tokens.
  • json::renderStr(text) returns text as 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 | None

Parse 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 None rather than an exception: unbalanced brackets, trailing commas, single-quoted strings, leading zeros such as 01, 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: <>

See also: JsonValue, render

render

render(JsonValue v) -> string

Render 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) -> string

Quote 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

See also

  • JsonValue — A JSON value: null, a boolean, a number, a string, an array or an object.
  • parse — Parse JSON text.
  • render — Render a value as compact JSON text.