std/json

JSON values are a Json enum with a member for each kind of value:

enum Json do
    NULL
    BOOL(bool value)
    NUMBER(f64 value)
    STRING(str value)
    ARRAY(Json[] items)
    OBJECT(map[str, Json] fields)
end

A match takes any value apart; the functions below cover the usual lookups. Objects keep their fields in the order they were read or set.

import "std/json"

match parse_json(read_text("config.json")) do
    when OK(config) then
        print(json_text(json_get(config, "name")))
        print(json_number(json_get(config, "port"), 8080.0))
    when ERROR(message) then
        eprint("config.json: " + message)
end
Function Result
parse_json(text) a Result[Json]; an error says where, as in line 2, column 7: expected ',' or '}'
to_json(value) compact JSON text on one line
pretty_json(value) JSON text with one item or field per line, indented by two spaces
json_get(value, key) field key of an object, as an Option[Json]
json_item(value, index) item index of an array, as an Option[Json]
json_len(value) the number of items or fields; 0 for other values
json_text(found) the text of a STRING in an Option[Json], or ""
json_number(found, fallback) the number of a NUMBER, or fallback
json_bool(found, fallback) the value of a BOOL, or fallback
json_items(found) the items of an ARRAY, or none, for a for loop
json_object(), json_array() a new empty object or array
json_set(object, key, value) sets a field of an object
json_push(array, value) adds an item at the end of an array

The code that #[derive(Json)] writes converts values with these, which programs can use too:

Function Result
json_of_str(text) a STRING, or NULL for a null str
json_of_array(items, convert), json_of_map(fields, convert), json_of_option(value, convert) an ARRAY, OBJECT, or the value, with each item converted, NULL for null or NONE
json_to_str(value), json_to_f64(value), json_to_bool(value) the value as a Result, an error for another kind of value
json_to_array(value, convert), json_to_map(value, convert), json_to_option(value, convert) the items converted, as a Result whose error says which item failed
json_is_object(value) whether it is an OBJECT
json_string(value) the text of a STRING, as an Option[str]

The lookups take an Option[Json], so they chain: json_text(json_get(user, "name")) is "" when user has no name or is not an object. Numbers are written with the fewest digits that read back as the same f64, such as 0.1, 31, and 6.02e23. JSON has no NaN or infinity; they are written as null. Text cannot hold the character \u0000, so reading it is an error, as is nesting arrays and objects more than 500 deep.

parse_json(text, jsonc: true) reads JSONC, as editors' settings files have it: comments, // to the end of the line and /* ... */, and a comma after the last item or field.

Function Result
json_equal(a, b) whether two values are the same JSON: numbers by value, objects whatever the order of their fields
json_copy(value) a copy that shares no array or object with the value
parse_json_lines(text), to_json_lines(values) JSON Lines: one value a line, blank lines left out
json_at(document, pointer) the value a JSON Pointer (RFC 6901) points to, as "/items/0/name" or "#/items/0", as an Option[Json]
json_pointer(names), json_pointer_names(pointer) a pointer from its names, with ~ and / escaped, and back
json_patch(document, patch) the document with a JSON Patch (RFC 6902) applied, as a Result[Json]
json_merge_patch(target, patch) the target with a JSON Merge Patch (RFC 7386) applied

A patch's operations, add, remove, replace, move, copy, and test, are applied in order to a copy, so the document given is never changed, and an ERROR names the operation that failed: of kind "test_failed" for a test that does not hold, "invalid_patch" for the rest.

Reading and writing in parts

A document too large to hold, or one that arrives a part at a time, is read as a stream of events from a Reader, such as a file, and written to a Writer a part at a time:

import "std/json"
import "std/fs"

// The items of a large array, one at a time.
JsonStream s = json_stream_from(open_file("orders.json")?)
next_json_event(s)?                      // START_ARRAY
while true do
    match next_json(s)? do
        when SOME(order) then
            print(json_text(json_get(order, "id")))
        when NONE then
            break                        // the end of the array
    end
end
Function Result
json_stream(text), json_stream_from(source) a JsonStream over text, or over a Reader, which it reads as it needs more
next_json_event(s) the next JsonEvent: START_OBJECT, END_OBJECT, START_ARRAY, END_ARRAY, KEY(name), VALUE(value) for any other value, or END
next_json(s) the whole next value where the stream is: the next item of an array, a field's value after its KEY, or the next value at the top; NONE at the end of the array or of the text
json_writer(out), json_writer(out, pretty: true) a JsonWriter to a Writer
write_json_start_object(w), write_json_end_object(w), write_json_start_array(w), write_json_end_array(w) starts or ends an object or array
write_json_key(w, name) the name of the next field of an object
write_json_value(w, value) a whole value, as an item, a field's value, or at the top
finish_json(w) writes what is left and gives the bytes written, or an ERROR for a writer used out of order or left with something open

A stream takes sequence: true for several values one after another, as JSON Lines has them, and then reads values until the text ends; a writer puts each value at the top on a line of its own, so given several it writes JSON Lines. A stream also takes jsonc: true. An ERROR while reading says where, as parse_json does, and the stream then gives it on every call; the text must be UTF-8.