std::json
std::json provides an RFC 8259-style JSON parser plus deterministic JSON
emission over an index-based DOM.
Design rules#
Documentis the owning JSON DOM container.ValueIdis the stable handle for values inside aDocument.- Parsing is explicit about ownership:
doc.parse(s)borrows unescaped strings and number lexemes froms,doc.parse_owned(s)copies strings and number lexemes intodoc.- JSON emission is explicit:
doc.stringify(id)emits compact JSON,doc.stringify_pretty(id, indent)emits pretty JSON.- DOM construction is explicit:
doc.new_*()allocates owned JSON values inside the document,doc.array_append(...)anddoc.object_put(...)link those values into arrays and objects,doc.set_root_value(id)marks a built value as the document root.Documentintentionally does not implementSerialize(string)orParse(E, string):- JSON text is a format-specific rendering, not the plain textual identity of the DOM,
- and borrowed parsing requires an explicit ownership choice that a blanket
Type.parse(...) -> Selfprotocol would hide.
Exported API#
module std::json;
export type ValueId = i64;
export type ParseResult = std::result::Result(ValueId, ParseError);
error ParseError {
kind: int,
offset: i64,
line: i64,
column: i64,
}
struct Document {
root: ValueId,
err: ParseError,
}
impl Document {
public fn is_ok (self: &Document) -> bool;
public fn root_value (self: &Document) -> ValueId?;
public fn set_root_value (mut self: &Document, id: ValueId) -> bool;
public fn parse (mut self: &Document, s: string) -> ParseResult;
public fn parse_owned (mut self: &Document, s: string) -> ParseResult;
public fn new_null (mut self: &Document) -> ValueId?;
public fn new_bool (mut self: &Document, value: bool) -> ValueId?;
public fn new_string (mut self: &Document, s: string) -> ValueId?;
public fn new_number_i64 (mut self: &Document, value: i64) -> ValueId?;
public fn new_number_u64 (mut self: &Document, value: u64) -> ValueId?;
public fn new_number_lexeme (mut self: &Document, s: string) -> ValueId?;
public fn new_array (mut self: &Document) -> ValueId?;
public fn array_append (mut self: &Document, array: ValueId, value: ValueId) -> bool;
public fn array_append_null (mut self: &Document, array: ValueId) -> bool;
public fn array_append_bool (mut self: &Document, array: ValueId, value: bool) -> bool;
public fn array_append_string (mut self: &Document, array: ValueId, value: string) -> bool;
public fn array_append_number_i64 (mut self: &Document, array: ValueId, value: i64) -> bool;
public fn array_append_number_u64 (mut self: &Document, array: ValueId, value: u64) -> bool;
public fn array_append_number_lexeme (mut self: &Document, array: ValueId, value: string) -> bool;
public fn array_append_new_array (mut self: &Document, array: ValueId) -> ValueId?;
public fn array_append_new_object (mut self: &Document, array: ValueId) -> ValueId?;
public fn new_object (mut self: &Document) -> ValueId?;
public fn object_put (mut self: &Document, obj: ValueId, key: string, value: ValueId) -> bool;
public fn object_put_null (mut self: &Document, obj: ValueId, key: string) -> bool;
public fn object_put_bool (mut self: &Document, obj: ValueId, key: string, value: bool) -> bool;
public fn object_put_string (mut self: &Document, obj: ValueId, key: string, value: string) -> bool;
public fn object_put_number_i64 (mut self: &Document, obj: ValueId, key: string, value: i64) -> bool;
public fn object_put_number_u64 (mut self: &Document, obj: ValueId, key: string, value: u64) -> bool;
public fn object_put_number_lexeme (mut self: &Document, obj: ValueId, key: string, value: string) -> bool;
public fn object_put_new_array (mut self: &Document, obj: ValueId, key: string) -> ValueId?;
public fn object_put_new_object (mut self: &Document, obj: ValueId, key: string) -> ValueId?;
public fn tag (self: &Document, id: ValueId) -> int?;
public fn is_null (self: &Document, id: ValueId) -> bool;
public fn as_bool (self: &Document, id: ValueId) -> bool?;
public fn as_string (self: &Document, id: ValueId) -> string?;
public fn as_number_lexeme (self: &Document, id: ValueId) -> string?;
public fn number_as_i64 (self: &Document, id: ValueId) -> i64?;
public fn number_as_f64 (self: &Document, id: ValueId) -> f64?;
public fn array_len (self: &Document, id: ValueId) -> i64?;
public fn array_first (self: &Document, id: ValueId) -> ValueId?;
public fn next_sibling (self: &Document, id: ValueId) -> ValueId?;
public fn object_len (self: &Document, id: ValueId) -> i64?;
public fn object_first_member (self: &Document, id: ValueId) -> ValueId?;
public fn member_key (self: &Document, member: ValueId) -> string?;
public fn member_value (self: &Document, member: ValueId) -> ValueId?;
public fn member_next (self: &Document, member: ValueId) -> ValueId?;
public fn object_get (self: &Document, obj: ValueId, key: string) -> ValueId?;
public fn stringify (self: &Document, id: ValueId) -> std::result::Result(std::strings::String, std::memory::OutOfMemory);
public fn stringify_pretty (self: &Document, id: ValueId, indent: int) -> std::result::Result(std::strings::String, std::memory::OutOfMemory);
}
export fn number_as_i64 (doc: &Document, id: ValueId) -> i64?;
export fn number_as_f64 (doc: &Document, id: ValueId) -> f64?;
export fn stringify (doc: &Document, id: ValueId) -> std::result::Result(std::strings::String, std::memory::OutOfMemory);
export fn stringify_pretty (doc: &Document, id: ValueId, indent: int) -> std::result::Result(std::strings::String, std::memory::OutOfMemory);
export fn error_message (kind: int) -> string;
Notes:
- The exported free functions are thin compatibility wrappers around the
corresponding
Documentmethods. - Arrays and objects are stored as linked lists over
ValueIdindices because Silk currently does not yet support the reference-rich layout a pointer-based DOM would want. - The internal DOM storage now uses a private
std::json::dom_storage_well_formed(...)theory defined insidestd/json.slk, and the public document accessors attach that local contract directly. This theory is intentionally not exported because it describes the current JSON DOM table layout rather than a stable downstream abstraction.
Borrowed vs owned parse#
doc.parse(s) is the fast path:
- unescaped strings are borrowed directly from
s, - number lexemes are borrowed directly from
s, - escaped strings are decoded into allocations tracked by
doc.
doc.parse_owned(s) copies string and number storage into doc, so the parsed
document remains valid even after s is no longer needed.
Both methods:
- clear the document before parsing,
- return
Ok(root)on success andErr(ParseError)on failure, - update
doc.root/doc.err, - and report out-of-memory as
ERR_OUT_OF_MEMORY.
DOM construction#
Document can also be built directly instead of parsed from text.
doc.new_null(),doc.new_bool(...),doc.new_string(...),doc.new_number_i64(...),doc.new_number_u64(...), anddoc.new_number_lexeme(...)allocate owned leaf values insidedoc.doc.new_array()anddoc.new_object()allocate empty container values.doc.array_append(...)anddoc.object_put(...)link an existing value into a container and reject invalid parents, invalid child ids, duplicate-parent reuse, and obvious cycle creation.doc.array_append_*anddoc.object_put_*convenience helpers allocate the child value and link it in one step for the common scalar cases.doc.array_append_new_array(...),doc.array_append_new_object(...),doc.object_put_new_array(...), anddoc.object_put_new_object(...)allocate and link nested containers, then return the linked child id so downstream code can keep building without a separate temporary/link pair.doc.set_root_value(id)marks a built value as the root sodoc.is_ok()anddoc.root_value()work the same way as after parsing.
Construction ownership rules:
- Built strings and number lexemes are copied into storage owned by
doc. - A value can only belong to one array/object parent.
- Reusing the same value under multiple parents is rejected.
- Container cycles are rejected.
String and number access#
doc.as_string(id)returns the decoded JSON string view for a string node.doc.as_number_lexeme(id)returns the original number lexeme.doc.number_as_i64(id)succeeds only when the number was recognized as an exacti64.doc.number_as_f64(id)reparses the stored lexeme asf64.
The returned string values are borrowed views into either the original input
buffer or storage owned by doc.
Example#
import std::json;
fn main () -> int {
let mut doc: Document = Document{};
let root_r = doc.parse(`{"name":"silk","answer":42}`);
if root_r.is_err() {
return 1;
}
let root: i64 = match (root_r) {
Ok(v) => v,
Err(_) => 0 as i64,
};
let name_id_opt = doc.object_get(root, "name");
if name_id_opt == None {
return 2;
}
let name_id: i64 = name_id_opt ?? 0 as i64;
if (doc.as_string(name_id) ?? "") != "silk" {
return 3;
}
let answer_id_opt = doc.object_get(root, "answer");
if answer_id_opt == None {
return 4;
}
let answer_id: i64 = answer_id_opt ?? 0 as i64;
if (doc.number_as_i64(answer_id) ?? 0 as i64) != 42 {
return 5;
}
let out_r = doc.stringify(root);
match (out_r) {
Ok(mut out) => {
let s: string = out as string;
out.drop();
if s != `{"name":"silk","answer":42}` {
return 6;
}
return 0;
},
Err(_) => {
return 7;
},
}
}
Construction example:
import std::json;
fn main () -> int {
let mut doc: Document = Document{};
let root_opt = doc.new_object();
if root_opt == None {
return 1;
}
let root: i64 = root_opt ?? 0 as i64;
if !doc.set_root_value(root) {
return 2;
}
let items_opt = doc.new_array();
if items_opt == None {
return 3;
}
let items: i64 = items_opt ?? 0 as i64;
if !doc.object_put(root, "items", items) {
return 4;
}
let one_opt = doc.new_number_i64(1);
if one_opt == None {
return 5;
}
if !doc.array_append(items, one_opt ?? 0 as i64) {
return 6;
}
let out_r = doc.stringify(root);
match (out_r) {
Ok(mut out) => {
let ok: bool = out.as_string() == `{"items":[1]}`;
out.drop();
if !ok {
return 7;
}
return 0;
},
Err(_) => {
return 8;
},
}
}
Serializing a custom type with the convenience helpers:
import std::json;
struct User {
name: string,
active: bool,
age: i64,
}
impl User {
public fn append_json (self: &User, mut doc: &Document) -> i64? {
let root_opt = doc.new_object();
if root_opt == None {
return None;
}
let root: i64 = root_opt ?? 0 as i64;
if !doc.object_put_string(root, "name", self.name) {
return None;
}
if !doc.object_put_bool(root, "active", self.active) {
return None;
}
if !doc.object_put_number_i64(root, "age", self.age) {
return None;
}
let tags_opt = doc.object_put_new_array(root, "tags");
if tags_opt == None {
return None;
}
let tags: i64 = tags_opt ?? 0 as i64;
if !doc.array_append_string(tags, "cli") {
return None;
}
if !doc.array_append_string(tags, "chat") {
return None;
}
return Some(root);
}
}
Considerations#
- Streaming tokenization for very large inputs.
- Writer-style JSON emission that does not require building an intermediate string.
- JSON Pointer or similar query helpers for nested lookup paths.
Source repository · Edit this page · View Markdown