This library is in flux. APIs may change without notice. Generated from source with koruc 0.1.7 on 11/4/2026.

JSON

import std/json

std/json - JSON encoding and decoding

json.kz · 11 tors

std/json - JSON encoding and decoding - `~part parse` (json.parse.kz): `std/json:parse` reads the `ok` branch's destructure as the input schema and generates a bespoke parser per site. - `~part emit` (json.emit.kz): the `value:` record literal is the output schema; a direct writer is generated per site.

Phantom lifecycles

Derived from the phantom labels in the declarations below — state! issues an obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.

JsonObject 1 state building!
string 2 states emitted!

object.new

<building!> koru_std/json.kz:35

object.set

<building> koru_std/json.kz:53

object.finish

<!building> koru_std/json.kz:96

array.new

<building!> koru_std/json.kz:109

array.push

<building> koru_std/json.kz:127

array.finish

<!building> koru_std/json.kz:163

get.string

<*> koru_std/json.kz:175

~part parse koru_std/json.parse.kz

Koru Standard Library: the `parse` part of json.kz — schema-directed compile-time JSON parsing as control flow. Joined via `~part parse` in json.kz; merged into the json module at load. The `ok` branch's destructure IS the schema — the same license std/regex:match takes with named groups (the destructure names the payload; a type on a field is a conversion at the splice, and `sub` — meaningless for a text span — is a nested object here): std/json:parse(input: body) | ok { name: string, age: i64, addr: { city: string } } |> greet(name) | malformed { message, offset } |> report(message, offset) Field grammar: a bare leaf (`name`) delivers the value's raw JSON span; `string` delivers the decoded inner text (escapes resolved); `bool`, the int types, and the float types convert at the splice; `?T` admits a missing key or a literal null; `[]T` is an array of T (T may itself be `?`/`[]`/`{...}` — `{...}` inside type_text is a nested object shape); `name: { ... }` is a nested object. Undeclared keys are skipped (JSON convention — strict both-ways is a pinned follow-up); duplicate keys take the last; every declared required key must appear or the parse fails. Ownership is SCOPED: allocations (decoded strings, arrays) land on a per-site arena that is deinit'd when the branch's chain exits — field values are valid inside the branch and die with it (the same contract yyjson's `close` draws, enforced by scope instead of a phantom). A produced value that retains a field slice is dangling — hand long-lived data an owned copy before it leaves the branch. The transform compiles that schema into a bespoke recursive-descent parser: one generated fn per object shape, typed readers per field, a generic value-skipper for undeclared keys, and a dispatch that hands the assembled payload struct to the `ok` body or routes failure to `malformed`. No DOM is materialized — fields land as typed values in the branch. This is the direct competitor of a navigation API (yyjson's parse→root→get→as.* chain): schema known at compile time means the tree is never built. Keys are compared raw — an escape inside a JSON key does not match its identifier yet (cut 2). A destructure-less `| ok v` is recognizer mode: the input must be exactly one JSON value, and the binding carries the consumed span.

parse

comptimetransform <*> koru_std/json.parse.kz:42

~part emit koru_std/json.emit.kz

Koru Standard Library: the `emit` part of json.kz — schema-directed compile-time JSON emission as control flow. Joined via `~part emit` in json.kz; merged into the json module at load. `std/json:emit` is the write-side twin of `std/json:parse`: the record literal at the `value:` position IS the output schema — keys are the JSON key names, and each value expression's type is resolved at compile time from the bindings in scope at the site: std/json:emit(value: { service: service, port: port, tls: tls }) | ok body |> send(body) |> std/json:emit.free(text: body) A bare identifier `emit(value: cfg)` emits every field of a branch-payload record bound upstream (`| ok cfg` on a tor whose payload declares named fields). Value grammar: a binding name resolved through scopeAtSite (destructure fields, `| branch name` payload binds, `: name` return binds, impl input fields), a `binding.field` path into a record payload, a nested `{...}` record literal, or a string/number/bool/null literal. Supported field types: `string` (escaped at the splice), `bool`, the int types, the float types (non-finite emits `null` — JSON cannot spell inf/nan), `?T` (null or inner), `[]T`. A bare destructure leaf has no type — it cannot be resolved and is refused with a teaching error. Ownership: `ok` mints `string<emitted!>`, discharged ONLY by `std/json:emit.free` — the same contract as yyjson's `render`/`render.free`, one allocation, koru_allocator-owned, no arena. The output is independent of every input binding's lifetime. The transform emits no DOM and no builder tree: a per-site escaping writer, a number writer, and an inline byte-append sequence specialized to the resolved schema. The tor decl declares the USER surface: `emit(...): body` binds a string carrying <emitted!>, discharged by `emit.free`. The transform proc returns SiteResult internally — the decl's return is what the call site produces.

emit

comptimetransform <emitted!> koru_std/json.emit.kz:38

emit.free

<!emitted> koru_std/json.emit.kz:43

~part parsehelpers koru_std/json.parsehelpers.kz

Koru Standard Library: JSON — parse helpers. Joined via `~part helpers` in json.parse.kz; merged into the json.parse module at load. Holds the transform's helper container so the driver stays under the line wall.

~part parsesub koru_std/json.parsesub.kz

Koru Standard Library: JSON parse — sub-entry expansion. Joined via `~part sub` in json.parse.kz; merged into the json.parse module at load. A `{...}` spec written inline in a plain-brace schema arrives as spec text, not a sub-destructure; expanding it here binds the object's LEAVES (`addr:{ city }` binds `city`, not `addr`) — the same shape the sub-destructure form produces.