JSON Library
    The package json contains a library for serialization, deserialization, and manipulation of JSON
    data. The central part of the library is the class json.JsonValue that represents a JSON
    value.
JSON Values
    As mentioned above, the class json.JsonValue is used to represent arbitrary JSON values.
    An instance of JsonValue can be thought of as a variant that is specialized for JSON usage. In
    particular, it implements both the interface for an array and for a map, so that it is convenient
    to access the data without explicit typechecking. Furthermore, a JsonValue that is created with
    its default constructor is initially empty (i.e. null), but can become any type if you try to add
    data to it. This only works once, however. Once the JsonValue contains a value, it will throw an
    exception if used improperly.
Creating Values
    Creating a JsonValue that contains a scalar type (i.e. booleans, numbers, and strings) is done
    using one of its constructors. These constructors are marked cast constructors to allow the
    conversion to happen implicitly. Therefore, it is possible to do the following in Basic Storm:
JsonValue b = false; JsonValue n = 32; JsonValue d = 3.5; JsonValue s = "test";
    To create arrays or maps, the most convenient way is to create an empty JsonValue and start
    populating it. Note that JsonValue replicates the interface of Map<T> and Array<T>. There are
    also constructors that accept JsonValue[] and Str->JsonValue respectively for programs that
    produce the contents separately.
JsonValue array; array << 20 << 30; JsonValue object; object["key"] = "value"; object["number"] = 12; // or object.put("key", "value"); object.put("number", 12);
    Note that array and object above will both have the value null before elements are added to
    them. If you wish to create empty arrays and objects, you can use the static functions
    json.JsonValue emptyArray() and json.JsonValue emptyObject(). This is mainly a
    concern if you need to serialize the representation into empty objects or arrays down the line.
Inspecting Values
    Inspecting the contents of a JsonValue is similarly aimed at being similar to existing
    conventions, but without requiring explicit checks all the time. Instead, the API is designed to
    throw exceptions whenever the expectations of the program do not match the structure.
    The first part of inspecting the contents of a JsonValue are the typecast members. As with many
    other types in the standard library, these are named after the type casted to. All of these throw an
    exception if the JsonValue does not contain the expected type. They are as follows:
- 
        byte,int,nat,long,wordExtract an integer type. Note that this only works for types that were integer types from the start (e.g. in the serialization source). Creating a JsonValuefrom a floating point value and trying to extract an integer type will result in an error. Also note that values are stored internally ascore.Long.You can check if the JsonValuecontains an integer number usingisInteger.
- 
        float,doubleExtract a floating point type. Note that integer types are automatically converted to floating point types if required. Values are stored internally as core.Double.You can check if the JsonValuecontains a floating point number usingisNumber. Note that if the value contains an integer, bothisNumberandisIntegerwill return true.
- 
        strExtract a core.Str. You can check if the value contains a string usingisStr.
- 
        arrayExtract an array of contained elements. In general, this is only necessary when you wish to iterate through the contents of the container. The number of elements can be retrieved using count. You can check if the value contains a string usingisArray.
- 
        objectExtract a map of contained elements. In general, this is only necessary when you wish to iterate through the contents of the container. The number of elements can be retrieved using count. You can check if the value contains a string usingisObject.
    For arrays and objects, JsonValue additionally implements the interface for arrays and maps. As
    such, it is possible to access elements using the appropriate operators on the value directly. There
    is, however, one minor difference regarding the behavior of [] for objects. Namely, that []
    behaves like get in that it throws an exception when trying to read a key that does not exist.
Using this API, a JSON object can be inspected as below:
JsonValue array; array << 20 << 20.5; JsonValue object; object["a"] = "string"; object["b"] = array; for (k, v in object.object) { print("${k} -> ${v}"); } Str aValue = object["a"].str; JsonValue bValue = object["b"]; for (id, v in bValue.array) { print("${id}: ${v}"); } Double first = bValue[0].double; Double second = bValue[1].double;
    The json.JsonValue also contains a == operator to allow comparing arbitrary JSON
    values. It implements a deep comparison.
JSON Literals
    The library also provides a syntax extension that allows embedding JSON literals into Basic Storm
    code. Literals start with the keyword json and continues with either an object or an array using
    the standard JSON syntax. All parts of the JSON hierarchy can be replaced by arbitrary Basic Storm
    expressions except for the keys in object literals.
    Keys in JSON objects do not have to be enclosed in quotes if it only contains alphanumeric
    characters and underscores (which is otherwise required by JSON). As such, to use Basic Storm
    expressions in keys, either use the interpolated string syntax (i.e. "${expr}") or enclose the
    expression in ${expr}.
Below is an example of a json literal. Note that it captures values from the surrounding code.
Str s = "string"; Int i = 15; Str name = "keyname"; JsonValue value = json{ "normal key": "value", unquoted-key: s, array: [s, i, name], "${name}": "key is named 'keyname'", ${name + "!"}: "key is named 'keyname!'", };
Serialization
    The json.JsonValue class serializes JSON to a proper string representation using its
    toS method as usual. By default, the toS generates formatted JSON documents, using line breaks
    and indentation to make it easier to read the structure.
    The class provides overloads to change the formatting options. If one parameter is passed to toS,
    it indicates the indentation depth (in number of spaces). If zero is passed, it produces a single
    line, compact representation. A second parameter to toS indicates if keys in objects should be
    sorted alphabetically. Since Map<T> does not preserve the insertion order of elements in Storm,
    element ordering in objects is otherwise unpredictable.
    Similar options are available for the toS overload that accepts a StrBuf as its first parameter.
    However, the second parameter is a boolean that instructs if the compact representation should be
    used rather than a number since this overload uses the StrBuf's standard indentation mechanism.
    The output from the serialization is always ASCII (i.e. non-ascii characters are escaped). As such,
    it can be converted to binary data using core.io.Buffer toUtf8(core.Str str) without issues.
    Deserialization is provided via the function parseJson. There are two overloads, one that accepts
    a string and another that accepts a core.io.Buffer. The second one allows parsing
    UTF-8-encoded binary data before decoding it first.
Exceptions
    All exceptions thrown by the JSON library inherit from json.JsonError. There are two
    subtypes, json.JsonParseError that is thrown by the JSON parser, and
    json.JsonAccessError that is thrown on incorrect accesses to the json.JsonValue
    class. The latter of the two also captures a stacktrace to ease debugging.
Object Serialization
    The library additionally contains the decorator jsonSerializable that automatically generates code
    that converts between json.JsonValue and regular Storm classes to make it easier to
    consume and produce JSON data in a structured manner. This is not too dissimilar from the normal
    serialization mechanism.
To illustrate how the decorator works, consider the following class:
class Employee : jsonSerializable { Str name; Nat salary = 500; Str? speciality; }
    In this case, the jsonSerializable decorator adds the following members to the class:
class Employee : jsonSerializable { Str name; Nat salary = 500; Str? speciality; init(JsonValue json) { init { name = json["name"].str; salary = if (value = json.at("salary")) { value.nat; } else { 500; }; speciality = { element = json["speciality"]; if (element.isNull) { Str?(); } else { Str?(element.str); } }; } } JsonValue toJson() { var out = JsonValue:emptyObject(); out.put("name", JsonValue(name)); out.put("salary", JsonValue(salary)); if (speciality) { out.put("speciality", JsonValue(speciality)); } else { out.put("speciality", JsonValue()); } return out; } }
    As we can see, jsonSerializable adds a constructor that converts a JsonValue into the type, as
    well as a toJson function that converts the type into JSON. As such, we can use the functions as
    follows:
var src = json{ "name": "Test", "salary": 1000, "speciality": null }; var converted = src.Employee; // or var converted = Employee(src);
    To convert back, we can of course just call converted.toJson.
    It is worth noting that the deserialization will only allow members that have explicit default
    values set to be missing from the JSON. For example, salary is allowed to be missing, while
    speciality is not allowed to, even though it is a maybe type.
    Finally, even though it is not illustrated above, the serialization library supports serializing and
    deserializing other types that have an appropriate constructor and a toJson function. It also
    supports arrays, maps with string keys, and maybe types natively. Inheritance is also supported, but
    since the actual type of an object is not stored in the JSON representation, the support is not as
    robust as the serialization library in Storm. That is, if we would serialize a subclass to
    Employee using its toJson, we will always get Employee if we deserialize it using
    json.Employee() since the system does not know which subclass was originally serialized.
