JSON API

A standards-compliant JSON encoder and decoder that stringifies Zym values to JSON text and parses JSON text back into Zym values.

Overview

The global identifier JSON is a namespace. Its statics stringify, parse, and create all operate directly on Zym values: stringify takes a Zym value and produces a JSON text string, and parse takes a JSON text string and produces a Zym value. JSON.create() returns a stateful instance whose methods are invoked as j.method(...) and which retains error information and, optionally, the original text.

Conventions

Type Mapping

ZymJSON
nullnull
booltrue / false
numberJSON number (always written with decimal precision)
stringJSON string
listJSON array
mapJSON object

Zym maps are string-keyed by construction, so encoded objects always have string keys. Parsed objects are returned as Zym maps; numeric or other non-string keys (which standard JSON cannot produce) would be coerced to strings.

Numbers

Zym numbers are doubles; integral values round-trip losslessly up to 253. JSON numbers always parse back to Zym numbers.

Errors

Static JSON.parse returns null when the input is not valid JSON; it never raises. Instance j.parse(text) returns false on failure and exposes the error via j.errorLine() and j.errorMessage(). Bad argument types, such as passing a number to parse, raise a Zym runtime error of the form JSON.method(args) ....

Limits & Unsupported Values

Stringify and setData reject values that nest deeper than 512 levels with a runtime error. Closures, structs, enums, and other non-JSON Zym values raise a runtime error during stringify.

Lenient parsing: the parser accepts a few non-standard niceties such as trailing commas inside arrays and objects. If your data must be strictly RFC 8259, validate it before parsing.

Static Methods

JSON.stringify(value, indent?, sortKeys?, fullPrecision?)

Encodes value as JSON text. Output is compact by default, and object keys are sorted by default for stable output. Passing an indent string pretty-prints the result. Values that nest deeper than 512 levels, or that contain non-JSON values such as closures, structs, or enums, raise a runtime error.

Returns: A JSON text string.

pretty-print
print(JSON.stringify({"a": 1, "b": [true, null]}, "  "))
// {
//   "a": 1.0,
//   "b": [
//     true,
//     null
//   ]
// }
JSON.parse(text)

Parses text as JSON and returns the resulting Zym value. Returns null on any parse error; it never raises on malformed input.

Returns: The parsed Zym value, or null on any parse error.

defensive parse
var v = JSON.parse("{not valid}")
if (v == null) {
    print("bad input")
}
JSON.create()

Builds a new stateful parser that retains error information and, optionally, the original input text.

Returns: A JSON instance whose methods are invoked as j.method(...).

Instance Methods

Returned by JSON.create(). Methods are invoked as j.method(...).

State: instance state persists across calls. A successful parse clears the error fields, and a failing parse does not overwrite the previously stored data.
j.parse(text, keepText?)

Parses text. Returns true on success and stores the result for retrieval with j.data(). On failure, populates j.errorLine() and j.errorMessage() and returns false. When keepText is true, the original input is retained and available from j.parsedText().

detailed errors
var j = JSON.create()
if (j.parse("{\"k\":[1,2,]}", true) == false) {
    print(j.errorLine())     // line of the offending token
    print(j.errorMessage())  // e.g. "Expected value, got ',' "
    print(j.parsedText())    // the original input
}
j.data()

Returns the most recently parsed value, or the value assigned via j.setData(), or null if nothing has been stored.

j.setData(value)

Replaces the stored data. Useful when an instance is being threaded through code that later wants to introspect or stringify the value. Rejects values that nest deeper than 512 levels with a runtime error.

Returns: null.

j.parsedText()

Returns the original input from the last parse(text, true) call, or "" if the text was not retained.

j.errorLine()

Returns the 0-indexed line number of the last parse error, counting newlines crossed before the offending token, or 0 after a successful parse. Errors on the first line therefore also report 0.

j.errorMessage()

Returns a human-readable message for the last parse error, or "" after a successful parse.

Examples

One-Shot Encode / Decode

var text = JSON.stringify({"name": "ada", "skills": ["math", "code"]})
print(text)   // {"name":"ada","skills":["math","code"]}

var v = JSON.parse(text)
print(v["name"])         // ada
print(v["skills"][1])    // code

Round-Trip a Nested Map

var original = {
    "items": [{"id": 1, "name": "a"}, {"id": 2, "name": "b"}],
    "count": 2
}
var txt = JSON.stringify(original)
var back = JSON.parse(txt)
print(back["items"][1]["name"])   // b
print(back["count"])              // 2