Skip to content

std/json ​

Strict RFC 8259 JSON: a parser with typed accessors, a cursor API that navigates without allocating, and builders (Json.obj(), Json.arr()) for writing. Json.parseJsonc accepts comments and trailing commas.

For typed decoding into structs use @derive(Json) (see the language reference): it generates T.fromJson(text) for an object and T.fromJsonArray(text) for a document whose top level is an array of T, with toJson / T.toJsonArray(items) the other way.

milo
from "std/json" import { Json }

Example ​

milo
from "std/json" import { Json }

fn main() {
    let doc = Json.parse("{\"name\": \"milo\", \"tags\": [\"fast\", \"safe\"], \"meta\": {\"stars\": 9}}")!

    print(doc["name"].str() ?? "unknown")         // milo
    print(doc["tags"][1].str() ?? "")             // safe
    print(doc["meta"]["stars"].i64() ?? 0)        // 9
    print(doc["missing"]["deeper"].exists())      // false

    for tag in doc["tags"].items() {
        print(doc[tag].str() ?? "")
    }

    // {...meta, stars: 10, lang: "milo"}
    let out = Json.obj().spread(doc, doc.curPath("meta")).int("stars", 10).str("lang", "milo").build()
    print(out)                                    // {"stars":10,"lang":"milo"}
}

doc["a"][0]["b"] indexes with a string key, an integer position, or a JsonCur, and ends in a read: .str(), .f64(), .i64(), .bool(), .len(), .exists(), .raw(), .json(), .items(), .keys(). A missing step is not an error; every read after it is None (or empty), so ?? default finishes the walk. It compiles to the cursor methods below with no allocation per step. A step stored in a variable is a JsonCur, which is only a position: a struct cannot hold the &Json it points into, so it is read back through the document as doc[cur]["field"]. The chain must start at a Json held in a variable or field.

A dotted path is one step: doc.at("data.user.orgs[0].login") starts a chain and doc[cur].at("stats.members") continues one (keys, and [n] for an array position, the syntax of curPath). An integer doc.at(i) is the owned element accessor instead.

The owned accessors (get, at) return a subtree as its own Json, a deep clone per call. That reads well and is fine for configuration-sized input; on a large document navigated in a loop, use indexing or the cursor methods (curRoot, curField, curChild, curStr, strAt, ...) instead.

API reference ​

Json ​

milo
pub struct Json

A parsed document: one flat pool of nodes over the original source, plus the index side-tables the accessors walk. Treat the fields as internal and use the accessors.

Fields: source: string, nodes: Vec<JsonNode>, nums: Vec<f64>, childIdx: Vec<i64>, keyOffsets: Vec<i64>, keyLens: Vec<i64>, root: i64.

Json.arr ​

milo
fn Json.arr(): JsonArr

New empty array builder.

Json.asBool ​

milo
fn Json.asBool(self: &Json): Option<bool>

This node as a boolean, or None if it is not one.

Json.asF64 ​

milo
fn Json.asF64(self: &Json): Option<f64>

This node as a float, or None if it is not a number.

Json.asI64 ​

milo
fn Json.asI64(self: &Json): Option<i64>

This node as an integer, or None if it is not one. Fractional, exponent and out-of-range literals are None, as for i64.

Json.asStr ​

milo
fn Json.asStr(self: &Json): Option<string>

This node as a string, or None if it is not one.

Json.at ​

milo
fn Json.at(self: &Json, index: i64): Option<Json>

Element index of an array as its own Json, or None if this is not an array or the index is out of range. Deep-clones like get.

Json.bool ​

milo
fn Json.bool(self: &Json, key: &string): Option<bool>

The boolean value of object key key, or None if absent or not a boolean.

Json.boolAt ​

milo
fn Json.boolAt(self: &Json, cur: i64, p: &string): Option<bool>

Boolean at path p under cursor cur, or None (see strAt).

Json.boolPath ​

milo
fn Json.boolPath(self: &Json, p: &string): Option<bool>

Json.curAt ​

milo
fn Json.curAt(self: &Json, start: i64, p: &string): i64

curPath relative to a cursor: doc.curAt(item, "cc.location"). A path that starts with an index ("[0].name") reads the cursor as an array.

Json.curBool ​

milo
fn Json.curBool(self: &Json, cur: i64): Option<bool>

Json.curChild ​

milo
fn Json.curChild(self: &Json, cur: i64, index: i64): i64

Json.curField ​

milo
fn Json.curField(self: &Json, cur: i64, key: &string): i64

Json.curFloat ​

milo
fn Json.curFloat(self: &Json, cur: i64): Option<f64>

Json.curHolds ​

milo
fn Json.curHolds(self: &Json, cur: i64): bool

Whether cur is a cursor into THIS document (and in range). Branch on this for a cursor you did not navigate to yourself; the accessors abort on a foreign cursor instead. False for -1. Mirrors Sealed.holds.

Json.curInt ​

milo
fn Json.curInt(self: &Json, cur: i64): Option<i64>

Json.curItems ​

milo
fn Json.curItems(self: &Json, cur: i64): Vec<i64>

Element cursors of the array at cur, so a walk reads for it in doc.curItems(a). Empty for anything that is not an array.

Json.curJson ​

milo
fn Json.curJson(self: &Json, cur: i64): Json

The node at cur as a document of its own (null for a missing cursor): what a Json field of a @derive(Json) struct decodes to.

Json.curJsonVal ​

milo
fn Json.curJsonVal(self: &Json, cur: i64): JsonVal

The node at cur as JsonVal.JRaw of its source text (null for a missing cursor): what a JsonVal field of a @derive(Json) struct decodes to.

Json.curKeyAt ​

milo
fn Json.curKeyAt(self: &Json, cur: i64, index: i64): string

Key of the object child at index. keys() answers the same question for the ROOT only, which is no help to a nested object whose keys are not known ahead of time — decoding a HashMap<string, V> field has to walk children it cannot name. Empty string for a non-object cursor or an out-of-range index; "" is also a legal JSON key, so bound the walk with curLen rather than testing the result.

Json.curKind ​

milo
fn Json.curKind(self: &Json, cur: i64): i32

Node kind at the cursor: 0 null, 1 bool, 2 number, 3 string, 4 array, 5 object; -1 if the cursor is invalid.

Json.curLen ​

milo
fn Json.curLen(self: &Json, cur: i64): i64

Json.curPath ​

milo
fn Json.curPath(self: &Json, p: &string): i64

Cursor at the end of a dotted path — "a.b", "items[0].name". A missing key, an out-of-range index or a shape mismatch yields -1, which every cursor accessor already reads as "nothing here", so the walk is total for any input. A key containing '.' or '[' is not addressable this way; use get()/at().

Json.curRaw ​

milo
fn Json.curRaw(self: &Json, cur: i64): string

The JSON text of the node at cur, as rawStr gives for a whole document; "" for a missing cursor. Splicing a subtree into output this way costs one walk of that subtree, where get/at plus rawStr copied the entire document first.

Json.curRoot ​

milo
fn Json.curRoot(self: &Json): i64

get/at above return an owned Json by deep-cloning the whole document (source string + every node) per call — navigating a large doc thousands of times blows up to gigabytes. The cursor API instead hands out an i64 cursor into THIS document: navigation returns cursors (-1 = missing) with zero allocation, and only leaf-string reads materialize (just that one string). Start at curRoot() and chain: let user = doc.curField(doc.curChild(doc.curRoot(), 0), "name") match doc.curStr(user)

A cursor is not a bare node index: the document's brand rides in its high bits (see jsonCurResolve above). Resolving a cursor against a different Json used to read a wrong-but-in-bounds node of the other document and report nothing; now it aborts, naming the mistake. -1 still flows through every accessor as "nothing here", so navigation stays total for any in-document miss. A memberwise copy of a Json shares its brand, which is sound because copying preserves node numbering; the documents get/at/path return carry a fresh brand because extraction renumbers the pool.

This is one of two ways to reach a nested value. The other is to chain the owned accessors with Option.andThen — doc.at(0).andThen((j) => j.str("name")) — which reads better but pays one deep clone per hop. Both replaced the family of fixed-shape accessors (strAt, childI64At, …) this module used to carry; see docs/breaking-changes.md.

Json.curStr ​

milo
fn Json.curStr(self: &Json, cur: i64): Option<string>

Json.curUint ​

milo
fn Json.curUint(self: &Json, cur: i64): Option<u64>

Unsigned read, for values above i64::MAX that curInt must reject.

Json.curValueAt ​

milo
fn Json.curValueAt(self: &Json, cur: i64, index: i64): i64

Value of the object child at index, the other half of curKeyAt. Deliberately separate from curChild, which is the ARRAY accessor and returns -1 for an object: "element i of an array" and "entry i of an object" are different questions, and one function answering both would make an object walked as an array look like it worked.

Json.f64 ​

milo
fn Json.f64(self: &Json, key: &string): Option<f64>

The numeric value of object key key as a float, or None if absent or not a number.

Json.f64At ​

milo
fn Json.f64At(self: &Json, cur: i64, p: &string): Option<f64>

Number at path p under cursor cur, or None (see strAt).

Json.f64Path ​

milo
fn Json.f64Path(self: &Json, p: &string): Option<f64>

Json.get ​

milo
fn Json.get(self: &Json, key: &string): Option<Json>

The value of object key key as its own Json, or None if this is not an object or has no such key. Deep-clones the subtree on every call; for hot navigation use the cursor API (curRoot, curField, ...).

Json.i64 ​

milo
fn Json.i64(self: &Json, key: &string): Option<i64>

The integer value of object key key, or None if absent or not an integer. Only a literal that is an integer and fits i64 answers Some: 1.5, 1e3 and out-of-range values are None, never a truncation.

Json.i64At ​

milo
fn Json.i64At(self: &Json, cur: i64, p: &string): Option<i64>

Integer at path p under cursor cur, or None (see strAt).

Json.i64Path ​

milo
fn Json.i64Path(self: &Json, p: &string): Option<i64>

Json.idxBool ​

milo
fn Json.idxBool(self: &Json, at: JsonCur): Option<bool>

.bool() at the end of an index chain: the boolean at at, or None.

Json.idxCur ​

milo
fn Json.idxCur(self: &Json, at: JsonCur): JsonCur

doc[cur]: re-enter an index chain at a cursor kept in a variable.

Json.idxExists ​

milo
fn Json.idxExists(self: &Json, at: JsonCur): bool

.exists() at the end of an index chain: whether every step found something.

Json.idxF64 ​

milo
fn Json.idxF64(self: &Json, at: JsonCur): Option<f64>

.f64() at the end of an index chain: the number at at, or None.

Json.idxI64 ​

milo
fn Json.idxI64(self: &Json, at: JsonCur): Option<i64>

.i64() at the end of an index chain: the integer at at, or None.

Json.idxItems ​

milo
fn Json.idxItems(self: &Json, at: JsonCur): Vec<JsonCur>

for it in doc[...].items(): a cursor per element of the array at at; empty for anything that is not an array.

Json.idxJson ​

milo
fn Json.idxJson(self: &Json, at: JsonCur): Json

.json() at the end of an index chain: the subtree at at as a document of its own (as curJson).

Json.idxKey ​

milo
fn Json.idxKey(self: &Json, at: JsonCur, key: &string): JsonCur

doc[...]["key"]: the member named key of the object at at; absent when at is not an object or has no such key.

Json.idxKeys ​

milo
fn Json.idxKeys(self: &Json, at: JsonCur): Vec<string>

Object keys in document order, for for k in doc["m"].keys().

Json.idxKind ​

milo
fn Json.idxKind(self: &Json, at: JsonCur): i32

.kind() at the end of an index chain: the kind tag curKind reports; negative when absent.

Json.idxLen ​

milo
fn Json.idxLen(self: &Json, at: JsonCur): i64

.len() at the end of an index chain: element or member count; 0 for a scalar or an absent value.

Json.idxPath ​

milo
fn Json.idxPath(self: &Json, at: JsonCur, p: &string): JsonCur

doc.at("a.b[0].c") / doc[...].at(path): the value at a dotted path under at (the syntax of curPath); absent when any step is missing.

Json.idxPos ​

milo
fn Json.idxPos(self: &Json, at: JsonCur, i: i64): JsonCur

doc[...][i]: element i of the array at at; absent when out of range or not an array.

Json.idxRaw ​

milo
fn Json.idxRaw(self: &Json, at: JsonCur): string

.raw() at the end of an index chain: the value's JSON text (as curRaw); empty when absent.

Json.idxRoot ​

milo
fn Json.idxRoot(self: &Json): JsonCur

doc["a"][0]["b"].str() is rewritten by the checker into these calls, rooted at doc every step: a JsonCur cannot read anything alone, because a struct cannot hold the &Json it points into. A cursor kept in a variable re-enters the chain as doc[cur]. A missing step yields a cursor every read treats as absent.

Json.idxStr ​

milo
fn Json.idxStr(self: &Json, at: JsonCur): Option<string>

.str() at the end of an index chain: the string at at, or None.

Json.idxValueAt ​

milo
fn Json.idxValueAt(self: &Json, at: JsonCur, i: i64): JsonCur

The value of the ith member of the object at at, in document order (pairs with idxKeys).

Json.isArray ​

milo
fn Json.isArray(self: &Json): bool

Json.isBool ​

milo
fn Json.isBool(self: &Json): bool

Json.isNull ​

milo
fn Json.isNull(self: &Json): bool

Json.isNum ​

milo
fn Json.isNum(self: &Json): bool

Json.isObject ​

milo
fn Json.isObject(self: &Json): bool

Json.isStr ​

milo
fn Json.isStr(self: &Json): bool

Json.keys ​

milo
fn Json.keys(self: &Json): Vec<string>

Every key of an object, in document order.

Json.len ​

milo
fn Json.len(self: &Json): i64

Number of elements of an array or members of an object.

Json.obj ​

milo
fn Json.obj(): JsonObj

New empty object builder: Json.obj().set("k", ...).build().

Json.parse ​

milo
fn Json.parse(s: &string): Result<Json>

Parse strict RFC 8259 JSON. Propagate failures with ? or match on the error. Takes a borrow because most callers hold the text in a buffer they still need; the document owns its source, so this copies once. A caller done with its owned buffer can hand it to jsonParse and skip the copy.

Json.parseJsonc ​

milo
fn Json.parseJsonc(s: &string): Result<Json>

Parse JSON with JSONC extensions (comments, trailing commas). Borrows like parse; jsoncParse is the owned form.

Json.path ​

milo
fn Json.path(self: &Json, p: &string): Option<Json>

Json.pretty ​

milo
fn Json.pretty(self: &Json, indent: i64): string

The document re-rendered with indent spaces per level (0 or less minifies). Reformats rawStr(), so what comes out is the same bytes the parse went in with, only re-spaced (see jsonPretty).

Json.rawStr ​

milo
fn Json.rawStr(self: &Json): string

The raw JSON text of this node.

Json.str ​

milo
fn Json.str(self: &Json, key: &string): Option<string>

The string value of object key key, or None if absent or not a string.

Json.strAt ​

milo
fn Json.strAt(self: &Json, cur: i64, p: &string): Option<string>

Typed reads at a path under a cursor: doc.strAt(cc, "location.route") for doc.curStr(doc.curField(doc.curField(cc, "location"), "route")).

Json.strPath ​

milo
fn Json.strPath(self: &Json, p: &string): Option<string>

JsonArr ​

milo
pub struct JsonArr

Fluent array builder: jsonArr().str("a").int(2).obj(jsonObj().bool("ok", true)).build()

Fields: items: Vec<JsonVal>.

JsonArr.arr ​

milo
fn JsonArr.arr(self: JsonArr, val: JsonArr): JsonArr

JsonArr.bool ​

milo
fn JsonArr.bool(self: JsonArr, val: bool): JsonArr

JsonArr.build ​

milo
fn JsonArr.build(self: &JsonArr): string

JsonArr.buildPretty ​

milo
fn JsonArr.buildPretty(self: &JsonArr, indent: i64): string

build() with indent spaces per nesting level (0 or less minifies).

JsonArr.float ​

milo
fn JsonArr.float(self: JsonArr, val: f64): JsonArr

JsonArr.int ​

milo
fn JsonArr.int(self: JsonArr, val: i64): JsonArr

JsonArr.nil ​

milo
fn JsonArr.nil(self: JsonArr): JsonArr

JsonArr.obj ​

milo
fn JsonArr.obj(self: JsonArr, val: JsonObj): JsonArr

JsonArr.raw ​

milo
fn JsonArr.raw(self: JsonArr, json: &string): JsonArr

Splice a pre-serialized JSON value verbatim (caller guarantees validity).

JsonArr.str ​

milo
fn JsonArr.str(self: JsonArr, val: &string): JsonArr

JsonArr.val ​

milo
fn JsonArr.val(self: JsonArr, val: JsonVal): JsonArr

JsonCur ​

milo
pub struct JsonCur

A position inside a parsed Json: what doc["a"][0] evaluates to. Only doc[...] reads it (see Json.idxRoot), so it is a plain copyable number.

Fields: c: i64.

JsonError ​

milo
pub enum JsonError

Variants:

  • Syntax(string): The text was not JSON at all — the parser's own message. Path is empty.
  • Missing(string): A required field was absent. Payload is the dotted path.
  • Mismatch(JsonMismatch): A value was present at the path but of the wrong JSON kind.

JsonError.message ​

milo
fn JsonError.message(self: &JsonError): string

JsonError.path ​

milo
fn JsonError.path(self: &JsonError): string

Dotted path to the offending value, "" for a syntax error.

JsonError.under ​

milo
fn JsonError.under(self: JsonError, seg: string): JsonError

Re-root the error one level up. Decoders build paths bottom-up so the success path never allocates a path string.

JsonError.underIndex ​

milo
fn JsonError.underIndex(self: JsonError, index: i64): JsonError

JsonEvent ​

milo
pub enum JsonEvent

Emits one event at a time and never materializes the tree — O(depth) memory, for documents too large to hold as a flat pool (cf. Go json.Decoder.Token / Rust StreamDeserializer). jsonPull(src).next() yields tokens until End.

Variants:

  • StartObject
  • EndObject
  • StartArray
  • EndArray
  • Key
  • Str
  • Num
  • Bool
  • Null
  • End
  • Error

JsonMember ​

milo
pub struct JsonMember

Fields: key: string, val: JsonVal.

JsonMismatch ​

milo
pub struct JsonMismatch

Where a decode failed and what was found instead. expected and actual share one vocabulary: null, bool, number, string, array, object, "missing".

Fields: path: string, expected: string, actual: string.

JsonNode ​

milo
pub struct JsonNode

kind: 0=null 1=bool 2=number 3=string 4=array 5=object

24 bytes, down from 56 with one named field per kind: the pool is the parse's working set, and the wider node cost 10-15% of parse time in cache misses. The two payload words mean something different per kind; only the jsonNode* accessors and jsonNew* constructors below know the layout. null: a = 0, b = 0 bool: a = 1 for true, 0 for false string: a = strStart, b = strLen number: a = index into the document's nums table, b = (strStart << 32) | strLen array, object: a = childStart, b = (keyStart << 32) | childCount (keyStart 0 for arrays) The packed halves fit because jsonParse caps the input at 2^31-1 bytes and the pool at 2^32-1 nodes: every count fits the low 32 bits, and every source offset and key index (at most one per input byte) stays below 2^31, so the high half never reaches the sign bit. That matters because >> on i64 is an arithmetic shift: a high half of 2^31 or more would unpack negative.

Fields: kind: i32, a: i64, b: i64.

JsonObj ​

milo
pub struct JsonObj

Fluent object builder. Methods consume and return self to enable chaining: jsonObj().str("type", "capabilities").int("seq", 3).build()

Fields:

  • members: Vec<JsonMember>
  • merging: bool: Set once spread ran: from then on a key already present is overwritten in place, which is what {...a, k: v} does in JS. Off by default so plain building stays a push, not a scan per key.

JsonObj.arr ​

milo
fn JsonObj.arr(self: JsonObj, key: &string, val: JsonArr): JsonObj

JsonObj.bool ​

milo
fn JsonObj.bool(self: JsonObj, key: &string, val: bool): JsonObj

JsonObj.boolOpt ​

milo
fn JsonObj.boolOpt(self: JsonObj, key: &string, val: Option<bool>): JsonObj

JsonObj.build ​

milo
fn JsonObj.build(self: &JsonObj): string

JsonObj.buildPretty ​

milo
fn JsonObj.buildPretty(self: &JsonObj, indent: i64): string

build() with indent spaces per nesting level (0 or less minifies).

JsonObj.float ​

milo
fn JsonObj.float(self: JsonObj, key: &string, val: f64): JsonObj

JsonObj.floatOpt ​

milo
fn JsonObj.floatOpt(self: JsonObj, key: &string, val: Option<f64>): JsonObj

JsonObj.int ​

milo
fn JsonObj.int(self: JsonObj, key: &string, val: i64): JsonObj

JsonObj.intOpt ​

milo
fn JsonObj.intOpt(self: JsonObj, key: &string, val: Option<i64>): JsonObj

JsonObj.nil ​

milo
fn JsonObj.nil(self: JsonObj, key: &string): JsonObj

JsonObj.obj ​

milo
fn JsonObj.obj(self: JsonObj, key: &string, val: JsonObj): JsonObj

JsonObj.put ​

milo
fn JsonObj.put(self: &mut JsonObj, key: string, val: JsonVal)

Append member key; after spread, overwrite an existing key in place.

JsonObj.raw ​

milo
fn JsonObj.raw(self: JsonObj, key: &string, json: &string): JsonObj

Splice a pre-serialized JSON value verbatim (caller guarantees validity).

JsonObj.spread ​

milo
fn JsonObj.spread(self: JsonObj, doc: &Json, cur: i64): JsonObj

{...src, k: v}: copy every member of the object at cur (values verbatim), after which a key added again replaces its value and keeps its position. A non-object cursor contributes nothing, as spreading null does in JS.

JsonObj.str ​

milo
fn JsonObj.str(self: JsonObj, key: &string, val: &string): JsonObj

JsonObj.strOpt ​

milo
fn JsonObj.strOpt(self: JsonObj, key: &string, val: Option<string>): JsonObj

Optional-field helpers: add the key only when the Option is Some, so a conditional field stays inside the fluent chain instead of breaking out to an if around each call.

JsonObj.val ​

milo
fn JsonObj.val(self: JsonObj, key: &string, val: JsonVal): JsonObj

JsonPull ​

milo
pub struct JsonPull

Container stack entries: 0 = array, 1 = object expecting key, 2 = object expecting value. The colon/comma transitions flip 1<->2 so a string can be classified as Key vs Str without lookahead.

Fields: src: string, pos: i64, stack: Vec<i64>.

JsonPull.next ​

milo
fn JsonPull.next(self: &mut JsonPull): JsonToken

JsonToken ​

milo
pub struct JsonToken

Fields:

  • kind: JsonEvent
  • text: string: set for Key and Str
  • num: f64: set for Num
  • flag: bool: set for Bool

JsonVal ​

milo
pub enum JsonVal

Serialization — JsonVal value tree + fluent builders. Complements the flat-pool parser above: parse with jsonParse, build with jsonObj()/jsonArr(), serialize with .build() / jsonSer.

Variants:

  • JNull
  • JBool(bool)
  • JInt(i64)
  • JFloat(f64)
  • JStr(string)
  • JArr(Vec<JsonVal>)
  • JObj(Vec<JsonMember>)
  • JRaw(string): Pre-serialized JSON spliced verbatim — the caller guarantees validity. Lets builders compose with fragments produced elsewhere (other builders, pass-through payloads) without a parse/rebuild round trip.

JsonVal.toJsonText ​

milo
fn JsonVal.toJsonText(self: &JsonVal): string

Method form of jsonSer, so @derive(Json) output needs no free-function import.

Functions ​

jsonBoolStr ​

milo
pub fn jsonBoolStr(b: bool): string

true/false as JSON text.

jsoncParse ​

milo
pub fn jsoncParse(s: string): Result<Json>

jsonEscapeStr ​

milo
pub fn jsonEscapeStr(s: &string): string

jsonF32Str ​

milo
pub fn jsonF32Str(f: f32): string

jsonFloatStr for an f32, printed at f32 precision (0.1 stays "0.1").

jsonFloatStr ​

milo
pub fn jsonFloatStr(f: f64): string

A float as JSON text. JSON has no NaN or Infinity (RFC 8259 6), so a non-finite value encodes as null, as JSON.stringify and serde_json do; printing it would emit nan/inf, which no parser (this one included) reads back.

jsonKindName ​

milo
pub fn jsonKindName(kind: i32): string

Human-readable name for a curKind result, including -1 for "no such node".

jsonParse ​

milo
pub fn jsonParse(s: string): Result<Json>

jsonPretty ​

milo
pub fn jsonPretty(src: &string, indent: i64): string

Re-render a JSON document. indent > 0 indents nested values by that many spaces per level, exactly as JSON.stringify(v, null, indent) does; indent <= 0 minifies, dropping every byte of whitespace outside a string.

Text that does not start with '{' or '[' comes back unchanged: a bare scalar has nothing to indent, and text that is not JSON at all (a plain-text error line, an empty body) is not this function's to mangle.

jsonPull ​

milo
pub fn jsonPull(src: string): JsonPull

jsonQuote ​

milo
pub fn jsonQuote(s: &string): string

Quote and escape a string as a JSON string literal, brackets included.

jsonSer ​

milo
pub fn jsonSer(v: &JsonVal): string