JSON
A small JSON parser and emitter built around one tagged-union type, JsonValue. UTF-8 in, UTF-8 out. Build trees with static constructors, mutate them with obj_set / arr_push, read them back with get / at / the typed get_* / opt_* accessors, and round-trip with JsonValue::parse / emit.
Import
import stdlib::json::*;
Public surface at a glance
| Item | Kind | Summary |
|---|---|---|
JSON_NULL … JSON_OBJECT |
pub const i32 |
Kind tags, 0–6. |
JsonValue |
pub struct |
The tagged-union value node. |
JsonParser |
pub struct |
Parser cursor (rarely constructed directly). |
JsonBind |
pub trait |
Two-way struct ⇄ JSON binding. |
null/bool/int/float/string/array/object |
static ctors | Allocate a fresh *JsonValue. |
parse/emit/escape |
parse/serialise | Document ⇄ string. |
arr_push/obj_set |
mutators | Build arrays/objects in place. |
get/at/len |
navigation | Untyped child access. |
as_string/as_int/as_float/as_bool/is_null |
scalar accessors | Coercing reads, never fail. |
get_string/get_int/get_bool/get_float/get_object/get_array |
typed (-> !T) |
Type-checked required reads. |
opt_string/opt_int/opt_bool/opt_float |
typed (-> ?T) |
Type-checked optional reads. |
get_any |
typed | Pass-through of any kind. |
Kind constants
Every JsonValue carries a kind: i32 tag drawn from these pub const values.
| Constant | Value | JSON type |
|---|---|---|
JSON_NULL |
0 |
null |
JSON_BOOL |
1 |
true / false |
JSON_INT |
2 |
integer number |
JSON_FLOAT |
3 |
floating-point number |
JSON_STRING |
4 |
string |
JSON_ARRAY |
5 |
array |
JSON_OBJECT |
6 |
object |
let v: *JsonValue = JsonValue::parse("[1,2,3]");
if v.kind == JSON_ARRAY { println!("array of", v.len()); }
The JsonValue struct
pub struct JsonValue {
pub kind: i32,
pub b: bool,
pub i: i32,
pub f: f64,
pub s: string,
pub arr: *Vector<JsonValue>,
pub obj_keys: *Vector<string>,
pub obj_vals: *Vector<JsonValue>,
}
Only the field matching kind is meaningful: b for JSON_BOOL, i for JSON_INT, f for JSON_FLOAT, s for JSON_STRING, arr for JSON_ARRAY, and the parallel obj_keys / obj_vals vectors for JSON_OBJECT. Prefer the accessor methods below over touching fields directly; the typed accessors enforce the right kind.
Constructors (static)
Each constructor allocates a fresh heap *JsonValue.
| Function | Signature | Description |
|---|---|---|
null |
fn null() -> *JsonValue |
JSON null. |
bool |
fn bool(b: bool) -> *JsonValue |
JSON boolean. |
int |
fn int(n: i32) -> *JsonValue |
JSON integer (stays an int until parsed back as a float). |
float |
fn float(f: f64) -> *JsonValue |
JSON float; always emits as a float, even for whole values. |
string |
fn string(s: string) -> *JsonValue |
JSON string; escaped at emit time, so pass raw text. |
array |
fn array() -> *JsonValue |
Empty array; fill with arr_push. |
object |
fn object() -> *JsonValue |
Empty object; fill with obj_set. |
println!(JsonValue::null().emit()); // null
println!(JsonValue::bool(true).emit()); // true
println!(JsonValue::int(42).emit()); // 42
println!(JsonValue::float(3.14).emit()); // 3.140000
println!(JsonValue::string("hi").emit()); // "hi"
Parsing and serialising
parse
pub fn parse(src: string) -> *JsonValue
Parses a JSON document into a tree. The parser is lenient: malformed input may produce a partially populated tree rather than an error (there is no !T result). Validate the resulting kind if the input is untrusted.
let v: *JsonValue = JsonValue::parse("{\"name\":\"alice\",\"age\":30}");
println!(v.get("name").as_string()); // alice
println!(v.get("age").as_int()); // 30
Lenient recovery in practice: truncated objects keep the keys parsed so far (a value with no following token becomes null), and input that is not JSON at all yields a JSON_NULL node.
import stdlib::json::*;
fn main() -> i32 {
let v: *JsonValue = JsonValue::parse("{\"a\":1,\"b\":");
println!(v.kind == JSON_OBJECT); // true
println!(v.len()); // 2 (b -> null)
let bad: *JsonValue = JsonValue::parse("not json");
println!(bad.kind == JSON_NULL); // true
return 0;
}
emit
pub fn emit(self: *JsonValue) -> string
Serialises to a compact (no whitespace) string. The result is heap-owned by the caller. Floats render with format!'s default f64 formatting (e.g. 3.140000). A null pointer emits as "null".
let o: *JsonValue = JsonValue::object();
o.obj_set("x", JsonValue::int(1));
o.obj_set("y", JsonValue::array());
println!(o.emit()); // {"x":1,"y":[]}
escape
pub fn escape(s: string) -> string
Wraps s in double quotes and escapes JSON-special bytes — ", \, CR, LF, tab, and other control bytes (< 32, rendered as ?). This is the same routine emit uses for string values and object keys.
println!(JsonValue::escape("hi")); // "hi"
println!(JsonValue::escape("a\nb")); // "a\nb"
Building
arr_push
pub fn arr_push(self: *JsonValue, v: *JsonValue)
Appends v to a JSON array. No-op when self is not an array.
obj_set
pub fn obj_set(self: *JsonValue, key: string, v: *JsonValue)
Sets key → v on a JSON object. No-op when self is not an object.
let user: *JsonValue = JsonValue::object();
user.obj_set("name", JsonValue::string("alice"));
user.obj_set("age", JsonValue::int(30));
println!(user.emit()); // {"name":"alice","age":30}
let a: *JsonValue = JsonValue::array();
a.arr_push(JsonValue::int(1));
a.arr_push(JsonValue::int(2));
println!(a.emit()); // [1,2]
Nesting just stores child *JsonValue nodes as values — there is no special API for nested arrays/objects, you obj_set / arr_push a freshly built child:
import stdlib::json::*;
fn main() -> i32 {
let root: *JsonValue = JsonValue::object();
root.obj_set("ok", JsonValue::bool(true));
let nums: *JsonValue = JsonValue::array();
nums.arr_push(JsonValue::int(1));
nums.arr_push(JsonValue::int(2));
nums.arr_push(JsonValue::float(2.5));
root.obj_set("nums", nums);
let addr: *JsonValue = JsonValue::object();
addr.obj_set("city", JsonValue::string("Rio"));
root.obj_set("addr", addr);
root.obj_set("note", JsonValue::null());
println!(root.emit());
// {"ok":true,"nums":[1,2,2.500000],"addr":{"city":"Rio"},"note":null}
return 0;
}
Navigation
| Method | Signature | Returns |
|---|---|---|
get |
fn get(self: *JsonValue, key: string) -> *JsonValue |
The value for key, or null if absent / not an object. |
at |
fn at(self: *JsonValue, idx: i32) -> *JsonValue |
The element at idx, or null if out of range / not an array. |
len |
fn len(self: *JsonValue) -> i32 |
Array element count, object key count, or 0 for scalars / null. |
get and at hand back a fresh heap copy of the child value, so it is safe to keep using after the parent goes out of scope. Both return a Glide null pointer on a miss — compare against null directly.
let v: *JsonValue = JsonValue::parse("{\"name\":\"alice\"}");
println!(v.get("name").as_string()); // alice
if v.get("nope") == null { println!("missing"); }
let arr: *JsonValue = JsonValue::parse("[10, 20, 30]");
println!(arr.at(1).as_int()); // 20
if arr.at(99) == null { println!("out of range"); }
println!(arr.len()); // 3
Scalar accessors
These never fail — they coerce or fall back to a zero value when the kind does not match. Use them when you already know (or do not care about) the type.
| Method | Signature | Fallback |
|---|---|---|
as_string |
fn as_string(self: *JsonValue) -> string |
"" for non-strings / null. |
as_int |
fn as_int(self: *JsonValue) -> i32 |
Floats truncate toward zero; non-numbers → 0. |
as_float |
fn as_float(self: *JsonValue) -> f64 |
Ints coerce to f64; non-numbers → 0.0. |
as_bool |
fn as_bool(self: *JsonValue) -> bool |
false for non-bools / null (no truthy coercion). |
is_null |
fn is_null(self: *JsonValue) -> bool |
true for JSON null or a Glide null pointer. |
println!(JsonValue::float(3.9).as_int()); // 3
println!(JsonValue::int(7).as_float()); // 7.0
println!(JsonValue::int(1).as_bool()); // false (no truthy coercion)
println!(JsonValue::null().is_null()); // true
Typed object accessors
These read a named field with type checking. Each comes in a required form returning a Result (!T) and, for the scalars, an optional form returning an Option (?T).
Required fields (-> !T)
| Method | Signature | Errors when |
|---|---|---|
get_string |
fn get_string(self, key: string) -> !string |
key missing, value null, or not a string. |
get_int |
fn get_int(self, key: string) -> !i32 |
key missing, value null, or not a JSON int (a JSON 3.0 is the wrong type). |
get_bool |
fn get_bool(self, key: string) -> !bool |
key missing, value null, or not a bool. |
get_float |
fn get_float(self, key: string) -> !f64 |
key missing or value null; a JSON int promotes to f64. |
get_object |
fn get_object(self, key: string) -> !*JsonValue |
key missing, value null, or not an object. |
get_array |
fn get_array(self, key: string) -> !*JsonValue |
key missing, value null, or not an array. |
Calling any get_* on a non-object self returns err("not an object"). Missing keys produce missing key "<key>"; type mismatches produce field "<key>" is not <want>. Propagate with ? or inspect .ok / .err / .val.
import stdlib::json::*;
fn main() -> !i32 {
let v: *JsonValue =
JsonValue::parse("{\"name\":\"alice\",\"age\":30,\"active\":true,\"score\":3.14}");
let name: string = v.get_string("name")?;
let age: i32 = v.get_int("age")?;
let on: bool = v.get_bool("active")?;
let sc: f64 = v.get_float("score")?;
println!(name, age, on, sc);
let r: !i32 = v.get_int("missing");
if !r.ok { println!(r.err); } // missing key "missing"
return ok(0);
}
get_object / get_array return the raw child *JsonValue so you can drill further. This is the idiomatic way to read nested documents:
import stdlib::json::*;
fn main() -> !i32 {
let src: string =
"{\"user\":{\"name\":\"alice\",\"age\":30,\"active\":true},\"tags\":[\"x\",\"y\",\"z\"]}";
let v: *JsonValue = JsonValue::parse(src);
let user: *JsonValue = v.get_object("user")?;
let name: string = user.get_string("name")?;
let age: i32 = user.get_int("age")?;
let on: bool = user.get_bool("active")?;
println!(name, age, on); // alice 30 true
let tags: *JsonValue = v.get_array("tags")?;
for let i: i32 = 0; i < tags.len(); i++ {
println!(tags.at(i).as_string()); // x, then y, then z
}
return ok(0);
}
Error surfacing without ? — inspect .ok / .err and branch:
import stdlib::json::*;
fn main() -> i32 {
let v: *JsonValue = JsonValue::parse("{\"age\":\"30\"}");
let r: !i32 = v.get_int("age"); // age is a string
if !r.ok { println!(r.err); } // field "age" is not an int
let m: !string = v.get_string("name"); // absent
if !m.ok { println!(m.err); } // missing key "name"
let scalar: *JsonValue = JsonValue::int(5);
let bad: !i32 = scalar.get_int("x"); // self is not an object
if !bad.ok { println!(bad.err); } // not an object
return 0;
}
import stdlib::json::*;
fn main() -> i32 {
let v: *JsonValue = JsonValue::parse("{\"a\":3,\"b\":3.0}");
let bi: !i32 = v.get_int("b");
if !bi.ok { println!(bi.err); } // field "b" is not an int
let af: !f64 = v.get_float("a");
if af.ok { println!(af.val); } // 3.0 (int promoted)
let bf: !f64 = v.get_float("b");
if bf.ok { println!(bf.val); } // 3.0
return 0;
}
Optional fields (-> ?T)
| Method | Signature | none() when |
|---|---|---|
opt_string |
fn opt_string(self, key: string) -> ?string |
missing key, JSON null, or not a string. |
opt_int |
fn opt_int(self, key: string) -> ?i32 |
missing key, JSON null, or not a JSON int. |
opt_bool |
fn opt_bool(self, key: string) -> ?bool |
missing key, JSON null, or not a bool. |
opt_float |
fn opt_float(self, key: string) -> ?f64 |
missing key or not a number; a JSON int promotes to f64. |
Read them with match, .has / .val, or default with ??.
import stdlib::json::*;
fn main() -> i32 {
let v: *JsonValue = JsonValue::parse("{\"nick\":\"al\",\"n\":5}");
match v.opt_string("nick") {
some(s) => println!("nick=", s),
none() => println!("no nick"),
}
let port: i32 = v.opt_int("port") ?? 8080; // 8080 (default)
println!(port);
let fl: ?f64 = v.opt_float("n"); // int promotes to f64
if fl.has { println!(fl.val); } // 5.0
let nope: ?i32 = v.opt_int("nick"); // wrong type -> none
if !nope.has { println!("not an int"); }
return 0;
}
get_any
pub fn get_any(self: *JsonValue, key: string) -> *JsonValue
Reads any value at key regardless of JSON type (or null if self is not an object, or the key is absent) — useful for a dynamic pass-through field whose shape you do not want to constrain.
import stdlib::json::*;
fn main() -> i32 {
let o: *JsonValue = JsonValue::parse("{\"meta\":[1,2],\"n\":5}");
let raw: *JsonValue = o.get_any("meta");
if raw != null { println!(raw.emit()); } // [1,2]
let miss: *JsonValue = o.get_any("nope");
if miss == null { println!("absent"); }
return 0;
}
The JsonBind trait
pub trait JsonBind {
fn from_json(v: *JsonValue) -> !Self;
fn to_json(self: *Self) -> *JsonValue;
}
Two-way JSON binding for your own structs. Implementing JsonBind lets a type flow through the typed HTTP handler helpers (json_respond, HttpResponse::ok().json_of(...), the @handler proc macro — see the HTTP chapter) without hand-rolling parse/emit ceremony each time. from_json returns !Self so element validation propagates with ?; to_json builds a *JsonValue tree.
A full round-trip, including an optional field handled with opt_int / .has:
import stdlib::json::*;
struct Pet {
pub name: string,
pub age: ?i32,
}
impl JsonBind for Pet {
fn from_json(v: *JsonValue) -> !Pet {
if v.kind != JSON_OBJECT { return err("expected object"); }
return ok(Pet {
name: v.get_string("name")?,
age: v.opt_int("age"),
});
}
fn to_json(self: *Pet) -> *JsonValue {
let o: *JsonValue = JsonValue::object();
o.obj_set("name", JsonValue::string(self.name));
if self.age.has { o.obj_set("age", JsonValue::int(self.age.val)); }
return o;
}
}
fn main() -> !i32 {
let p: Pet = Pet::from_json(JsonValue::parse("{\"name\":\"rex\",\"age\":4}"))?;
println!(p.name); // rex
if p.age.has { println!(p.age.val); } // 4
println!((&p).to_json().emit()); // {"name":"rex","age":4}
let q: Pet = Pet::from_json(JsonValue::parse("{\"name\":\"tom\"}"))?;
println!((&q).to_json().emit()); // {"name":"tom"} (age omitted)
return ok(0);
}
Blanket impl for Vector<T>
impl<T: JsonBind> JsonBind for Vector<T>
Any Vector<T> whose T implements JsonBind is itself JSON-bindable: a JSON array maps to a Vector<T> and back. Element-level from_json failures bubble up via ?, so the first bad element fails the whole parse — there is no silent skipping. Empty arrays parse to empty vectors; non-array input errors with "expected array".
import stdlib::json::*;
@derive(JsonBind)
struct Pet { pub name: string, pub age: i32 }
fn main() -> i32 {
let xs: *Vector<Pet> = Vector::new();
xs.push(Pet { name: "rex", age: 4 });
xs.push(Pet { name: "tom", age: 7 });
let js: string = xs.to_json().emit();
println!(js); // [{"name":"rex","age":4},{"name":"tom","age":7}]
let good: !Vector<Pet> = Vector::from_json(JsonValue::parse(js));
if good.ok { println!(good.val.len()); } // 2
// one bad element fails the whole parse
let bad: !Vector<Pet> =
Vector::from_json(JsonValue::parse("[{\"name\":\"a\",\"age\":1},{\"name\":\"b\",\"age\":\"x\"}]"));
if !bad.ok { println!(bad.err); } // field "age" is not an int
// non-array input
let notarr: !Vector<Pet> = Vector::from_json(JsonValue::parse("{}"));
if !notarr.ok { println!(notarr.err); } // expected array
return 0;
}
The JsonParser struct
pub struct JsonParser {
pub src: string,
pub pos: i32,
pub n: i32,
}
The cursor JsonValue::parse drives internally. It is public but rarely constructed directly — prefer JsonValue::parse(src), which builds a JsonParser { src: src, pos: 0, n: src.len() } for you and returns the parsed tree.
See also
- HTTP chapter —
JsonBind-powered response helpers (json_respond,HttpResponse::ok().json_of(...), the@handlerproc macro) live on the HTTP side and consume the types documented here. - Hashmap chapter — JSON objects use insertion-ordered parallel vectors, not a hash map; reach for the hashmap module when you need keyed lookups at scale.
Caveats
JsonValue::parseis lenient and never returns an error; truncated or malformed input yields a best-effort tree (a stranded value becomesnull, unparseable input becomesJSON_NULL). Validatekind(or use theget_*accessors, which type-check) before trusting parsed data.\uXXXXescapes decode to a single?— full UTF-16 / surrogate decoding is not yet implemented.get_intrejects JSON floats (3.0) as the wrong type; useget_floatwhen a number may legitimately carry a fractional part.get_floataccepts ints.JsonValue::float(n)always emits with a fractional part (3.000000), never as a bare integer. UseJsonValue::intfor integral wire values.obj_setappends duplicate keys rather than overwriting;getreturns the first match. Build a fresh object to "replace" a value.- The coercing
as_*accessors cannot distinguish a missing key (null) from a real zero value; reach forget_*/opt_*when that matters.