A reusable JSON library for Bend 2 featuring machine-checked example laws, RFC 6901 JSON Pointer navigation, array combinators, typed extractors, configurable indentation, and streaming NDJSON.
Note on Verification & AI Development:
This library was primarily developed with AI assistance (using advanced language models), following Bend’s intended workflow of AI-generated code verified by formal proofs.
The laws inLAWS.bendare concrete example cases, mechanically checked bybend PROOF.bend.
An independent test suite (tests/run_tests.py) additionally compares the library against Python'sjsonmodule.
- JSON AST: Supports
null, booleans (true/false), non-negative integers (Nat) and negative integers (-Nat) up to 2^48 - 1 in magnitude, decimal and exponent numbers (kept verbatim asJRaw), strings with all RFC 8259 escapes (including\uXXXXand surrogate pairs), heterogeneous arrays, and key-value objects. Integers of 2^48 or more are rejected withFail{"number too large"}. - Machine-Checked Specifications: 60 formal laws in
LAWS.bend, including universally quantified properties and compile-time regression checks, all verified bybend PROOF.bend. - RFC 6901 JSON Pointer:
pointer(j, ptr): Deep querying supporting standard escaping (~1for/,~0for~) and array indexing.pointer_set(j, ptr, val): Returns the document with the value at the pointer path replaced.
- Rich Object Dictionary APIs:
get,has,set,remove,obj_len,keys,values,entries.merge(base, overrides): Object merging with override semantics.sort_keys(j): Deterministic canonical lexicographical key sorting.
- Array Operations & Combinators:
- Element access & mutation:
arr_get,arr_len,arr_push,arr_set,arr_pop,arr_remove,arr_insert. - Functional combinators:
arr_map,arr_filter,arr_fold.
- Element access & mutation:
- Ergonomic Typed Extractors:
- Type-safe extractors:
get_str,get_num,get_neg,get_bool,get_arr,get_obj. - Safe fallbacks:
get_str_or,get_num_or,get_bool_or.
- Type-safe extractors:
- Configurable Pretty Printing:
stringify(j): Compact serializer.pretty(j): Standard 2-space indented formatter.pretty_indent(j, step): Custom indentation width (e.g. 4 spaces).
- Validation & Streaming NDJSON:
validate(s) -> Bool: Returns whethersis valid JSON (implemented by runningparse).stringify_ndjsonandparse_ndjson: Line-delimited JSON stream processing.
- State-Machine Parsing:
- String parsing, line splitting, and pointer tokenization use explicit state machines instead of deep recursion. Tested with 20,000 levels of nesting (compact output) and 200,000-character strings. Very large inputs (hundreds of thousands of array elements) are slow.
Import directly by content hash without copying any files:
import Base
import 0xf776c27e08f75fc19a070c691bcbb111/json.bend as Json
Alternatively, place json.bend into your project directory and import it locally:
import Base
import ./json.bend as Json
If importing from a subdirectory (such as examples/):
import ../json.bend as Json
import Base
import ./json.bend as Json
def run_doc(j: Json.Json) -> IO(Unit):
+j = j
name = Json.get_str_or(j, "name", "unknown")
version = Json.get_num_or(j, "version", 0n)
active = Json.get_bool_or(j, "active", False{})
do IO<Unit>:
IO.print("Project: " ++ name ++ " v" ++ Nat.show(version))
IO.print("Active: " ++ Bool.show(active))
def main() -> IO(Unit):
raw = "{\"name\": \"Bend\", \"version\": 2, \"active\": true}"
match Json.parse(raw):
case Done{val}:
run_doc(val)
case Fail{err}:
do IO<Unit>:
IO.print("Error: " ++ err)
# Deep lookup
port = Json.pointer(doc, "/server/ports/0")
# Deep in-place mutation
updated = Json.pointer_set(doc, "/server/ports/0", Json.num(80n))
def double_num(j: Json.Json) -> Json.Json:
match j:
case Json.JNum{n}:
Json.num(Nat.mul(n, 2n))
case _:
j
# Maps double_num over every element
doubled = Json.arr_map(~double_num, my_array)
# Compact minified JSON
compact_str = Json.stringify(doc)
# 2-space indented pretty JSON
pretty_str = Json.pretty(doc)
# 4-space indented pretty JSON
custom_str = Json.pretty_indent(doc, 4n)
Runnable example programs are located in the examples/ directory:
| Example | Command | Description |
|---|---|---|
| Basic Usage | bend examples/01_basic_usage.bend |
Parsing, typed field extraction, mutation, and pretty printing. |
| JSON Pointer | bend examples/02_json_pointer.bend |
Deep RFC 6901 pointer lookups, escaping (~1), and pointer_set. |
| Array Transforms | bend examples/03_array_transforms.bend |
Array combinators: arr_map, arr_filter, arr_push, arr_len. |
| Streaming NDJSON | bend examples/04_ndjson_streaming.bend |
Streaming line-delimited JSON parsing and serialization. |
| Full Showcase | bend main.bend |
Comprehensive 9-section integration test suite. |
type Json is Data:
JNull{}
JBool{val: Bool}
JNum{val: Nat}
JNeg{val: Nat}
JStr{val: String}
JArr{vals: List<&2, Json>}
JObj{kvs: List<&2, Sigma<&2, &2, String, _ => Json>>}
JRaw{raw: String}
null:JNull{}bool(b: Bool) -> Json:JBool{b}num(n: Nat) -> Json:JNum{n}(non-negative integer)neg(n: Nat) -> Json:JNeg{n}(negative integer,-n)str(s: String) -> Json:JStr{s}arr(xs: List<&2, Json>) -> Json:JArr{xs}obj(kvs: List<&2, Sigma<&2, &2, String, _ => Json>>) -> Json:JObj{kvs}kv(key: String, val: Json) -> Sigma<&2, &2, String, _ => Json>: Key-value tupleraw(s: String) -> Json:JRaw{s}(decimal or exponent number stored verbatim). The text is not validated, so pass only valid JSON number text such as1.5or2e10.raw_checked(s: String) -> Maybe<&2, Json>: Validating constructor that verifiessparses as a standalone JSON number, returningSome{j}orNone{}.
is_null,is_bool,is_num,is_neg,is_str,is_arr,is_obj,is_raw: ReturnsBoolas_bool,as_num,as_neg,as_str,as_arr,as_obj,as_raw: ReturnsMaybe<&2, T>
get(j: Json, key: String) -> Maybe<&2, Json>: Lookup object property.has(j: Json, key: String) -> Bool: Check object property existence.set(j: Json, key: String, val: Json) -> Json: Upsert key-value pair.remove(j: Json, key: String) -> Json: Remove property by key.obj_len(j: Json) -> Nat: Number of object entries.keys(j: Json) -> List<&2, String>: Extract list of keys.values(j: Json) -> List<&2, Json>: Extract list of values.entries(j: Json) -> List<&2, Sigma<&2, &2, String, _ => Json>>: Extract key-value pairs.merge(base: Json, overrides: Json) -> Json: Merge two objects with override semantics.sort_keys(j: Json) -> Json: Lexicographical sort of object keys.
arr_len(j: Json) -> Nat: Count array elements.arr_get(j: Json, idx: Nat) -> Maybe<&2, Json>: Access element at 0-based index.arr_push(j: Json, item: Json) -> Json: Append item to array.arr_set(j: Json, idx: Nat, val: Json) -> Json: Replace element at index.arr_pop(j: Json) -> Pair(Maybe<&2, Json>, Json): Remove the trailing element. Returns(Some{last}, remaining_array), or(None{}, j)ifjis empty or not an array.arr_remove(j: Json, idx: Nat) -> Json: Remove element at index.arr_insert(j: Json, idx: Nat, val: Json) -> Json: Insert element at index.arr_map(~f: Json -> Json, j: Json) -> Json: Map function over elements.arr_filter(~f: Json -> Bool, j: Json) -> Json: Filter elements matching predicate.arr_fold(~f: Json -> Json -> Json, acc: Json, j: Json) -> Json: Left fold over elements.
pointer(j: Json, ptr: String) -> Maybe<&2, Json>: Deep navigation with RFC 6901 pointer syntax.pointer_set(j: Json, ptr: String, val: Json) -> Json: Returns the document with the value at the pointer path replaced. With the empty pointer""it replaces the whole document.
parse(s: String) -> Result<&2, &2, String, Json>: Parse JSON string into AST.stringify(j: Json) -> String: Compact JSON stringification with character escaping.pretty(j: Json) -> String: Formatted 2-space indented JSON string.pretty_indent(j: Json, step: Nat) -> String: Indented JSON with customizable indentation width.validate(s: String) -> Bool: Fast boolean syntax check.stringify_ndjson(items: List<&2, Json>) -> String: Format list of items as newline-delimited JSON.parse_ndjson(s: String) -> List<&2, Result<&2, &2, String, Json>>: Parse newline-delimited stream.
Bend's proof system verifies that code satisfies properties specified in LAWS.bend.
To verify all proofs:
bend PROOF.bendExpected output:
All terms check.arr_setandarr_removewith an out-of-range index return the array unchanged.arr_insertwith an index past the end appends the element.- Object keys keep their order and duplicate keys are preserved by
parseandstringify. stringifyescapes control characters below U+0020 (as\uXXXXor the short forms), so strings always serialize as valid JSON strings.- Integers of 2^48 or more are rejected by
parsewithFail{"number too large"}; decimals and exponent numbers are kept asJRawtext.
Laws in LAWS.bend are mechanically checked by bend PROOF.bend. Where tractable within the current Base library, properties are stated as universally quantified laws (for x: T) and proven by case analysis and structural induction. The remaining laws are explicitly documented as compile-time regression test examples.
-
Universally Quantified Laws:
- Type Guard Soundness (
is_null_sound,is_bool_true_sound,is_bool_false_sound,is_num_sound,is_neg_sound,is_str_sound,is_arr_sound,is_obj_sound,is_raw_sound,as_raw_sound): Proved universally for all inhabitants ofBool,Nat,String,List<Json>,List<Sigma<String, Json>>, etc. - Array Operations & Invariants (
arr_len_empty,arr_len_two,arr_get_zero,arr_set_sound,arr_pop_sound,arr_insert_sound,arr_remove_sound): Proved universally over arbitrary list tails, head elements, and values. - Object Operations & Dictionary Invariants (
get_found,get_missing,has_found,has_missing,obj_len_sound,remove_sound,merge_override,sort_keys_order): Proved universally over arbitrary values, keys, and key-value lists. - JSON Pointer Operations (
pointer_root,pointer_obj,pointer_arr,pointer_nested,pointer_escape,pointer_set_leaf): Proved universally over arbitrary documents and property values. - Typed Extractors (
get_str_sound,get_num_sound,get_num_or_sound): Proved universally over arbitrary strings, natural numbers, keys, and default values. - Round-Trip Base Cases (
roundtrip_null,roundtrip_bool,roundtrip_arr_empty,roundtrip_obj_empty): Proved universally for all inhabitants ofJNull,JBool{b},JArr{Nil}, andJObj{Nil}. - NDJSON Step Property (
ndjson_step): Proved universally for any head element and list of JSON documents.
- Type Guard Soundness (
-
Supporting Lemma Library (
Lemmas.bend):arr_len_append&obj_len_append: Inductive proofs that appending an element increments array length and object key count.- RFC 8259 §7 two-character escape inverses (
\",\\,\n,\t,\r,\b,\f) and control character escape/unescape round-trips (\u0000to\u001f). - UTF-16 surrogate pair encoding/decoding definitions and round-trips for scalar endpoints (U+10000, U+1F600, U+10FFFF).
- Digit character encode/decode round-trip proofs for all decimal digits (
0n..9n).
The remaining laws are preserved as honest, concrete compile-time regression tests. They cannot currently be generalized to universal proofs due to missing foundations in Bend's upstream Base library:
- Natural Number Round-Trip (
roundtrip_num_0,roundtrip_num_42,roundtrip_neg_7):- Blocker: Proving
for n: Nat { parse(stringify(JNum{n})) == Done{JNum{n}} }requires Euclidean division and remainder theorems forNat.divmod(showingq * 10 + r == nandr < 10), base-10 Horner accumulation lemmas, and parser fuel invariant proofs. UpstreamBase.bendcurrently has no generalNatarithmetic lemmas beyondWord.add_comm/U32.add_comm.
- Blocker: Proving
- String Round-Trip (
roundtrip_str_empty,roundtrip_str_hello):- Blocker: Proving
for s: String { parse(stringify(JStr{s})) == Done{JStr{s}} }requires structural induction over character lists, character comparison lemmas, and proof that the string parser's fuel (100n + 8 * len) is sufficient for any escape expansion.
- Blocker: Proving
- Array, Object, and Complex Document Round-Trip (
roundtrip_arr_items,roundtrip_obj_single,roundtrip_raw_decimal,roundtrip_complex):- Blocker: Mutual structural induction over recursive AST structures (
JsoncontainingList<Json>containingJson) requires dependent induction schemes and parser state machine transition invariants that are currently beyond Base's capabilities.
- Blocker: Mutual structural induction over recursive AST structures (
- Validation Predicates (
validate_valid,validate_invalid,validate_reject_*,pointer_missing):- Blocker: Formalizing validation for arbitrary strings requires a mechanized grammar specification of RFC 8259 and proofs that invalid prefixes cause deterministic parser rejections.
- Safety Net:
- The property-based and differential test suite in
tests/run_tests.pyserves as the primary verification safety net for these unproven areas, validating millions of operations against Python's oracle.
- The property-based and differential test suite in
# Machine-checked laws
bend PROOF.bend
# Independent conformance, scale and fuzz suite (needs Python 3 and `bend` on PATH)
python3 tests/run_tests.py
# or point it at a specific Bend command:
BEND="bun /path/to/bend2/main.ts" python3 tests/run_tests.pyThe suite compares parse, stringify, pretty and validate with Python's json module on RFC 8259 valid and invalid documents, large and deeply nested inputs, and seeded random documents. tests/api_checks.bend covers JSON Pointer (RFC 6901), objects, arrays and NDJSON.
This project is licensed under the Apache 2.0 License.