JSON for Bend 2.
With Bend alone there is nothing to
install: import ezjson by its hub name and version, and bend fetches it
from the hub into ~/.bend/lib on the first
run; no install step. ezjson is built and checked on Bend 2.0.34.
import emerging-ezjson@1.1.0.0/main.bend as Ezjson
emerging-ezjson@1.1.0.0 is ezjson v1.1.0 and resolves to
0x81c67699424929b5c44cd8577e18117f; to pin by content, import
0x81c67699424929b5c44cd8577e18117f/main.bend instead.
Or with ez, which records the
package in ez.toml (ez init makes one):
ez add Emerging-Patterns/ezjson
parse reads JSON text into Maybe Json: null, booleans, numbers, strings,
arrays, and objects. print writes that value back as compact text, and
writes U+FFFD for a code point no UTF-8 text can hold (a lone surrogate).
pretty writes the same tokens indented two spaces a level, one element or
member a line and a space after each colon, as JavaScript's
JSON.stringify(v, null, 2) does, with no newline at the end. A number
keeps the spelling it was parsed with. num builds one from that text, and
builds null when the text is not a JSON number. The Json type is
src/value.bend's; import that file to name it in a signature, and give a
match on a result its own def, since Bend matches only a parameter.
import emerging-ezjson@1.1.0.0/main.bend as Ezjson
import emerging-ezjson@1.1.0.0/src/value.bend as Value
def shown(got: Maybe<&2, Value.Json>) -> String:
match got:
case None{}:
""
case Some{j}:
Ezjson.print(j)
def compact(s: String) -> String:
shown(Ezjson.parse(s))
def point() -> String:
Ezjson.print(Ezjson.obj([("x", Ezjson.num("1")), ("y", Ezjson.num("-2")),
("name", Ezjson.str("a")), ("ok", Ezjson.bool(True{})),
("extra", Ezjson.null())]))
null, bool, str, num, arr, and obj build values. get reads an
object key and returns the first value when a key is repeated. at reads an
array index. as_bool, as_str, as_num, as_u32, and as_f32 read a
value of that kind, or none when the kind differs or the number does not fit.
has tells a missing key from a key whose value is null, and len counts
the elements of an array or the members of an object.
A multi-gigabyte text is read with a cursor. parse builds one tree;
cursor does not. next returns one event and the cursor after it. skip
drops the next value (one scalar, or one array or object and everything
inside it) without building that value. text copies the spelling of a
string, a key, or a number. Commas and colons are not events. Import
src/pull.bend to name the cursor and match the event constructors.
cursor holds the unread suffix of the source. A string or key span
(Pull.EStrS, Pull.EKeyS) is the first nn characters of a suffix, not
a copy. The event holds that suffix, so the span stays readable after
next advances, and it is gone when the event is dropped. A number
(Pull.ENum) keeps its own spelling, so one number in a big array does
not hold the rest of the array. Dropping the caller's own string variable
does not drop the cursor.
Walk a large object or array one event at a time. skip drops a subtree
you do not need. text copies an owned string when you need one.
import emerging-ezjson@1.1.0.0/main.bend as Ezjson
import emerging-ezjson@1.1.0.0/src/pull.bend as Pull
def owned.go(step: (Pull.Ev & Pull.Cur)) -> (String & Pull.Cur):
(ev, rest) = step
(Maybe.default(&2, String, Ezjson.text(ev), ""), Ezjson.skip(rest))
def owned(cur: Pull.Cur) -> (String & Pull.Cur):
owned.go(Ezjson.next(cur))
SPEC.md lists every behavior ezjson guarantees, by ID: conformance
to RFC 8259 and the behavior of
each def in main.bend. A row is either proved by a quantified law in
LAWS.bend, checked by bend PROOF.bend (the first line must be
ALL PROOFS CHECK), or listed in its trust boundary. A row marked pending is
not guaranteed yet. docs/rfc/ezjson-spec.md has the
reasoning and the rollout.
scale/ is a compiled size check that pull-walks large string-row and
nested-array documents without calling parse: 32000 string rows
(~563 KiB) and 35000 nested rows of [0.100,0.200,0.3] (~615 KiB).