marslang rs-0.19.1

std.json#

Reading and writing JSON, the text format most programs and web services exchange data in.

mars
takepkg std.json;

func m{
    data = json.parse("{\"name\": \"Ada\", \"tags\": [\"math\", \"code\"], \"age\": 36}");
    out(data.get("name"), data.get("tags")[1]);   // Ada code

    data.set("age", 37);
    out(json.stringify(data));   // {"name":"Ada","tags":["math","code"],"age":37}
}
Function Result
parse(text) The value the JSON text describes
stringify(value) The value as JSON on one line, with no spaces
pretty(value) The value as JSON across lines, two spaces per level
indented(value, spaces) As pretty, with 1 to 16 spaces per level
load(path) Reads a JSON file and parses it
save(path, value) Writes a value to a file as pretty JSON

Reading#

JSON Marslang
object map, keeping the order of its keys
array array
string string
whole number int when it fits in 32 bits, longint in 64, otherwise bigint
number with . or an exponent float
true, false, null themselves

Whole numbers follow the rule for number literals in a program, so 3000000000 is a longint and 123456789012345678901234567890 a bigint, with every digit kept. Only a number of more than about a million digits, beyond a bigint's limit, raises RangeError. A key that appears twice keeps its first position and its last value.

Reading is strict, as RFC 8259 says: no comments, no trailing commas, no single quotes, no NaN or Infinity, and no leading zeros. Text that is not JSON raises SyntaxError with where the problem is and what was expected:

text
SyntaxError: JSON line 3, column 3: expected a value, found 'o'
SyntaxError: JSON line 1, column 7: a comma before '}': JSON allows no trailing comma

Nesting deeper than 512 levels is refused with a SyntaxError rather than risking the interpreter's stack.

Writing#

mars
out(json.pretty(json.parse("{\"a\": [1, {}], \"b\": []}")));
text
{
  "a": [
    1,
    {}
  ],
  "b": []
}

Floats are written as the shortest text that reads back as the same number, and always with a . or an exponent, so 1.0 is written 1.0 and stays a float when read back. A bigint is written as a number with every digit, and reads back as the same bigint; some other readers, JavaScript's among them, round whole numbers beyond 2^53, which is why many services send such IDs as strings. Strings are written as they are, with quotes, backslashes, and control characters escaped; other characters, such as é or 😀, are written directly.

Some values have no JSON form and raise instead of being guessed at:

Value Raises Write instead
a map with a key that is not a string TypeError a map with string keys
a set or a pair TypeError an array
a family instance TypeError a map of the fields to write
a function or a package TypeError —
infinity or NaN RangeError a string, or null
a container that holds itself TypeError —

The same array can appear twice in a value; only one that contains itself is refused.

JSON has no dates, so a date, datetime, or duration is written as an ISO 8601 string, such as "2026-09-24" or "PT2H30M". Reading gives the string back; pass it to date(), datetime(), or duration().

Files#

mars
takepkg std.json;

func m{
    settings = json.load("settings.json");
    settings.set("theme", "dark");
    json.save("settings.json", settings);
}

load reads through std.file, so a missing file raises file.NotFoundError, and a parse error names the file:

text
SyntaxError: settings.json: JSON line 3, column 3: expected a value, found 'o'

save writes pretty JSON, two spaces per level, ending in a newline, and is all or nothing like file.write: a crash while saving leaves the old file whole.

Speed#

Reading and writing are native: a 1.9 MB document of 15,000 records is read in about 70 ms and written back in under 20 ms by a release build. Doing it in Marslang itself would also be wrong, not just slower: Marslang's strings count whole characters, and a quote written directly before a combining accent would join with it into one.

The wrapper is written in Marslang (std/json.mars) over the native package std/rs/json.rs.