commented
pub let commented : FlavorJSON with comments and trailing commas — what editors and their configuration files have settled on, tsconfig.json being the best-known.
One section per package, in the order the guide reads them. Each item is the declaration the compiler sees and the sentence the source wrote about it.
The tree every dialect reads into, the scanner they share, and the writer that puts it back as strict JSON.
pub let commented : FlavorJSON with comments and trailing commas — what editors and their configuration files have settled on, tsconfig.json being the best-known.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as UTF-8 bytes — the form a body goes onto a wire in.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero, the default, writes the compact form with no space anywhere; anything larger writes one line per member with that many spaces a level, and a space after each colon.
A number that came in as text goes back out as that text, so a document read and written again is unchanged. Only a number with no such text is formatted, and a number that is not finite — which JSON5 can produce and JSON cannot write — becomes null, as every JavaScript implementation does.
pub fn load(BytesView, Flavor, bom? : Bool) -> Json raise RefusedRead one document from bytes, which is how one arrives over a wire. A leading byte-order mark is skipped by default. RFC 8259 §8.1 forbids writing one and says nothing about refusing to read one, and refusing would mean rejecting half the files Windows tooling produces.
pub fn loads(StringView, Flavor) -> Json raise RefusedRead one document in the given dialect.
The whole input must be one value: anything after it, other than whitespace and — where the dialect allows them — comments, is Trailing, not silently ignored. A parser that stops at the first value turns a truncated file into a plausible one.
pub let relaxed : FlavorJSON5: everything ECMAScript 5 would have accepted.
pub let strict : FlavorRFC 8259 and nothing more.
pub(all) struct AtWhere something went wrong in a document that arrived as text.
The offset is in UTF-16 code units from the start, which is what a StringView indexes by; line and column are one-based, because that is how every editor counts and an error a person cannot find in their file is half an error.
at : Int32The offset in UTF-16 code units from the start of the document, which is what a StringView indexes by.
line : Int32The line the offset is on, counting from one.
column : Int32The column the offset is at, counting from one.
pub fn At::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn At::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn At::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
pub(all) enum DuplicatesWhat a repeated member name means.
A reader that fills a map keeps the last one, because that is what a map does, so that is the default. The other two are here because a document with two "id" members is usually a mistake, and a reader that wants to hear about it should not have to write its own parser.
LastThe last one wins. What everybody does.
FirstThe first one wins.
RejectA repeated name is an error.
pub fn Duplicates::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Duplicates::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Duplicates::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
pub(all) struct FlavorHow to read a document: what syntax to accept, and what to do at the edges.
The dialects in this family differ only in their syntax — the tree they produce is the same — so the scanner is written once and each dialect is a set of switches. A dialect outside this repository can be described the same way without forking anything.
The last three fields are not syntax but policy. They are here rather than as arguments because they are set once for a deployment and because this family has sixteen entry points: a policy added here costs one line, and a policy added to every signature costs sixteen.
comments : Bool// to end of line, and /* … */.
trailing_commas : BoolA comma before the closing brace or bracket.
unquoted_keys : BoolA member name written as an identifier rather than a string.
single_quotes : Bool'like this' as well as "like this".
extra_numbers : Bool+1, .5, 5., 0xFF, Infinity, -Infinity, NaN.
extra_escapes : Bool\x41, \0, \v, a backslash before a newline, and a backslash before anything else standing for that character.
extra_space : BoolThe whitespace ECMAScript recognises, not only the four RFC 8259 allows.
depth : Int32How deep the nesting may go before the document is refused.
Five hundred is past anything written for a reader and far short of what exhausts a stack on any backend. Readers disagree on the number — a thousand in one, a hundred and twenty-eight in another — but not on having one, because a document of a hundred thousand open brackets is a denial of service rather than a document.
surrogates : BoolWhether a lone half of a surrogate pair is a character.
The case is implementation-defined: a reader may keep the half, replace it with U+FFFD, or refuse the document. This one refuses, because a lone half cannot be written back out and every value that could replace it is a guess about what the writer meant.
duplicates : DuplicatesWhat a repeated member name means.
pub fn Flavor::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Flavor::new(comments? : Bool, trailing_commas? : Bool, unquoted_keys? : Bool, single_quotes? : Bool, extra_numbers? : Bool, extra_escapes? : Bool, extra_space? : Bool, depth? : Int32, surrogates? : Bool, duplicates? : Duplicates) -> SelfBuild a dialect by naming the parts that differ from strict JSON.
The same thing can be written { ..@moonjson.strict, depth: 100 }; this form exists because a named argument reads better when several parts differ.
pub fn Flavor::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Flavor::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
pub(all) suberror RefusedWhy a document was refused, and where.
One error type for the whole family — reading text, reading bytes, walking a pointer, applying a patch, writing a value a format cannot carry — so that a caller has one thing to catch and one place to look. A variant that has a position carries the At it was found at; a variant about a pointer or a patch names the pointer itself, because a move and a copy each carry two and an error that did not say which one would be half an error.
Unexpected(At, Char)A character that cannot start or continue what is being read.
Truncated(At)The document ends inside a value.
BadEscape(At)An escape sequence that is not one, or a lone half of a surrogate pair.
BadNumber(At)A number the grammar does not admit — a leading zero, a bare minus, an exponent with no digits.
Trailing(At)A second value after the first one ended.
TooDeep(At)Nesting past the depth limit. Without this, a document of ten thousand open brackets would take the stack down with it.
BadUtf8(At)Bytes that are not UTF-8, when reading a document that arrived as bytes.
Repeated(At, String)A member name that appears twice, when the dialect refuses that.
Short(at~ : Int32)The document ended inside a value.
Extra(at~ : Int32)Something after the value ended. A document is one value, and a reader that stops at the first turns a corrupt file into a plausible one.
Bad(at~ : Int32, why~ : String)A byte that cannot be read this way: a reserved additional-information value, a length or an index that does not fit, a string that is not UTF-8, a nesting that went too deep.
Unwritable(why~ : String)A value the format has no way to write: a number that is not finite into JSON, a byte string into a text dialect, a key that is not a string into one whose objects have only string keys.
Syntax(path~ : String, at~ : Int32)The pointer is not one: it does not start with a slash, or a ~ is followed by anything but 0 or 1. at is the offset within path.
Missing(path~ : String, token~ : String)What the pointer names is not there: a name no member has, or an index no array reaches.
NotContainer(path~ : String, token~ : String)The pointer walked into something that is neither an object nor an array.
BadIndex(path~ : String, token~ : String)An array index that is not one — not decimal digits without a leading zero — or - where it does not mean append.
Document(why~ : String)The document is JSON but not a patch: not an array of operations.
Operation(at~ : Int32, why~ : String)The operation at at is not one: no op member, an op that is not one of the six, or a member it requires missing or of the wrong type.
TestFailed(path~ : String)A test that did not hold.
IntoChild(from~ : String, path~ : String)A move into one of the moved value's own children (RFC 6902 §4.4).
Root(path~ : String)The operation names the root, which has no parent to take it from.
pub fn Refused::at(Self) -> At?Where the failure happened, when it happened in text.
None is not a failure to say: a pointer or a patch that was refused has a path rather than a line, and a line number invented for it would be worse than no line number at all.
pub fn Refused::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Refused::message(Self) -> StringThe failure as one sentence a person can read: what went wrong, and where.
A text dialect names the line and column, counting from one, because that is how an editor counts; a binary format names the byte offset, because that is all a file without lines has to offer. The sentence is lower case and carries no full stop, so a caller can put it after its own word — "refused: " + error.message() — without the punctuation fighting.
pub fn Refused::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Refused::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
The grammar exactly: no comments, no trailing comma, no leading zero, no unquoted name, no control character inside a string.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean — what is sometimes called ensure_ascii, except that it is off by default here, because a UTF-8 body is the normal case now. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one JSON document.
Strictly RFC 8259: a leading zero, a trailing comma, a comment or a control character inside a string is an error naming the line and column it is on, not something quietly accepted.
JSON plus // and /* */ comments and a trailing comma. Nothing else: a name is still quoted, a string is still double-quoted.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean — what is sometimes called ensure_ascii, except that it is off by default here, because a UTF-8 body is the normal case now. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one JSONC document: JSON, plus comments and a trailing comma.
This is what tsconfig.json and settings.json are written in. Comments are discarded — the tree has nowhere to put them — so writing the result back out loses them.
Everything ECMAScript 5.1 would have accepted: comments, trailing commas, unquoted names, single quotes, hexadecimal and signed numbers, Infinity and NaN, and strings continued across lines.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean — what is sometimes called ensure_ascii, except that it is off by default here, because a UTF-8 body is the normal case now. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one JSON5 document.
JSON5 admits values JSON cannot write — Infinity, NaN, a hexadecimal integer — so a document read here and written as strict JSON is not always the same document. write says what becomes of each.
One document to a line, separated by a line feed, each one complete. It is what a log file or a stream of records is written as, because a reader can start anywhere and a writer can append without rewriting.
pub fn dump_all(ArrayView[Json], ascii? : Bool, sort? : Bool) -> BytesThe same, as UTF-8 bytes.
pub fn dumps_all(ArrayView[Json], ascii? : Bool, sort? : Bool) -> StringWrite documents one to a line, each ending in a line feed.
Every line ends in one, including the last: a file that ends without it cannot be appended to safely, which is the format's whole purpose.
pub fn load_all(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Array[Json] raise @moonjson.RefusedThe same, from bytes.
pub fn loads_all(StringView, flavor? : @moonjson.Flavor) -> Array[Json] raise @moonjson.RefusedRead a stream of documents, one to a line.
A blank line is skipped rather than refused: files grown by appending end up with one, and refusing it would make a reader fail on its own writer's output. A carriage return before the line feed is also tolerated, because half the tools that produce these files run on Windows.
A string that names one place in a document. ~1 stands for a slash and ~0 for a tilde, in that order — undoing them the other way round would turn ~01 into a slash.
pub fn escape(StringView) -> StringWrite a name so it can go into a pointer: ~ first, then /.
pub fn get(Json, StringView) -> Json raise @moonjson.RefusedFollow a pointer and return what is there.
- as an array index is refused here even though RFC 6901 §4 names it: it means "one past the last element", which exists only for JSON Patch's add, and returning something for it would be inventing a value.
pub fn has(Json, StringView) -> BoolWhether a pointer leads anywhere. A malformed pointer is not an exception here — it simply does not lead anywhere.
pub fn join(ArrayView[String]) -> StringJoin names into a pointer, escaping each.
pub fn remove(Json, StringView) -> Json raise @moonjson.RefusedTake away whatever pointer leads to, and answer the document that results.
RFC 6902's remove: the member or the element must be there, because taking away what was never there is a request nobody can have meant. The root cannot be removed — an empty pointer names the whole document, and there would be nothing left to answer with.
pub fn set(Json, StringView, Json) -> Json raise @moonjson.RefusedPut value where pointer leads, and answer the document that results.
The semantics are RFC 6902's add and replace taken together: a member or an element that is already there is replaced, one that is not is created, and - as an array index appends — which is the one place RFC 6901 §4 says that token means something, and the reason get refuses it.
It answers a new document rather than changing the one it was given. A tree is shared: a value reached through two pointers is one value, and editing it in place would change both. Only the path from the root to the edit is rebuilt; everything else is the same value it was.
let cfg = @pointer.set(cfg, "/spec/replicas", Json::number(5))
pub fn tokens(StringView) -> Array[String] raise @moonjson.RefusedSplit a pointer into the names it walks through, undoing the escapes.
The empty pointer names the whole document and yields no tokens; "/" names the member whose name is empty and yields one.
A patch document is an array of operation objects, each naming one change — add, remove, replace, move, copy or test — against a target document. It is read into Operations rather than left as a tree of objects, because the six share nothing but their shape: two carry a value, two carry a second pointer, and a document whose op is misspelled is a document that would quietly do nothing.
pub fn apply(Json, ArrayView[Operation]) -> Json raise @moonjson.RefusedApply a patch to a document, and answer the document that results.
The operations are applied in order, each to the result of the one before it. A patch that fails partway applies nothing: the document it was given is never changed, and the answer is the error rather than half a document — RFC 6902 §5 says that if any operation is unsuccessful, application of the entire patch document is not deemed successful.
It answers a new document rather than changing the one it was given, for the same reason @pointer.set does: a tree is shared, and a value reached through two pointers is one value.
pub fn dump(ArrayView[Operation], indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
pub fn dumps(ArrayView[Operation], indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a patch document: the operations as the array of objects RFC 6902 §4 prints them, op first and from before path where a move or a copy has both.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Array[Operation] raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Array[Operation] raise @moonjson.RefusedRead a patch document: the array of operations it names.
The document itself is read as strict JSON — a patch is a protocol message rather than a settings file — and the dialect can still be named for a caller whose patches arrive from a JSONC source.
pub(all) enum OperationOne operation in a patch document, as RFC 6902 §4 names the six.
Add(path~ : String, value~ : Json)Put value where path leads (RFC 6902 §4.1).
An object member that is not there is created, one that is there is replaced, and an array element is inserted — the elements at or above the index shift one place to the right — with - appending at the end. The root is a target too: an add to the empty pointer makes the value the whole document.
Remove(path~ : String)Take away what path leads to (RFC 6902 §4.2).
It has to be there: taking away what was never there is a request nobody can have meant. The root cannot be taken away — the empty pointer names the whole document, and there would be nothing left to answer with.
Replace(path~ : String, value~ : Json)Replace the value at path (RFC 6902 §4.3).
The target has to be there already; this is add without the creating.
Move(path~ : String, from~ : String)Take the value at from away and put it at path (RFC 6902 §4.4).
from must not be a parent of path: a location cannot be moved into one of its own children.
Copy(path~ : String, from~ : String)Copy the value at from to path (RFC 6902 §4.5).
Test(path~ : String, value~ : Json)Require the value at path to equal value (RFC 6902 §4.6).
Equality is the tree's: a number equals the same number written another way, because the tree holds a number as a number and not as text.
pub fn Operation::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Operation::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Operation::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
A merge patch has no syntax of its own: the patch document is a JSON document, read with @json.loads like any other. What this package adds is the evaluation — the rules that say which members of the target are added, replaced or taken away — so there are no loads and dumps here. The four faces belong to formats, and the format is plain JSON.
pub fn merge(Json, Json) -> JsonApply a merge patch to a document, and answer the document that results.
A merge patch looks like the document it changes rather than like a list of instructions: a member that is not there is added, one that is there is replaced, and a member whose value is null is taken away — a null in the patch is an instruction, while a null already in the target is data and stays. A patch that is not an object replaces the whole document, null itself among them, and a target that is not an object is ignored and started again from an empty one.
It answers a new document rather than changing the one it was given, for the same reason @pointer.set does: a tree is shared, and a value reached through two pointers is one value.
let cfg = @json.loads("{\"host\":\"localhost\",\"port\":8080,\"tls\":false}")
@merge.merge(cfg, @json.loads("{\"port\":9090,\"tls\":null}"))
// {"host":"localhost","port":9090}A query is compiled once into selectors and then run against any number of documents: a string on every call would parse the same query for every node it touches, and a filter is evaluated once per node.
pub fn alike(Json, Json) -> BoolEquality of values: numbers by value — 1 and 1.0 are one number — and everything else structurally.
pub fn compile(StringView) -> Path raise @moonjson.RefusedCompile a query.
The grammar is RFC 9535 §2.2's, and a query that does not parse is refused with the offset it stopped at rather than run as something else. Nothing around the query is whitespace: " $" and "$ " are not queries.
pub fn finds(StringView, StringView) -> Bool raise @moonjson.RefusedWhether the pattern matches any part of the text.
pub fn matches(StringView, StringView) -> Bool raise @moonjson.RefusedWhether the pattern matches the whole of the text.
The dialect is I-Regexp (RFC 9485): ., the classes [...], \p{...} for the Unicode general categories, the four quantifiers, alternation and groups. \d, \w and \s and their negations are accepted as well, which I-Regexp itself leaves out; they are what a pattern written for another dialect reaches for first, and refusing them would be a surprise rather than a service.
pub fn select(Json, StringView) -> Array[Json] raise @moonjson.RefusedRun a query against a document, answering the nodes it selects in document order.
pub(all) enum OpA comparable operator.
EqEqual.
NeNot equal.
LtLess than.
LeLess than or equal.
GtGreater than.
GeGreater than or equal.
pub fn Op::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Op::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Op::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
pub(all) enum OperandOne side of a comparison, or the thing a test asks about.
Const(Json)A literal written in the query.
Nodes(root~ : Bool, segments~ : Array[Segment])A query: @ for the node under test, $ for the document.
Call(name~ : String, args~ : Array[Operand])One of the five functions RFC 9535 §2.4 defines.
pub(all) struct PathA compiled query.
segments : Array[Segment]The segments, from the first to the last.
pub fn Path::select(Self, Json) -> Array[Json]Run a compiled query against a document, answering the nodes it selects in document order. The same Path can be run against as many documents as you like, which is the reason to compile one.
pub(all) enum SegmentOne step of a query.
Child(Array[Selector])The selectors applied to the node itself.
Descendant(Array[Selector])The selectors applied to the node and to every node inside it, in document order — the .. segment.
pub(all) enum SelectorOne way of choosing from a node.
Name(String)A member with this name, or nothing.
Index(Int64)An element at this index, or nothing.
An index is an I-JSON integer, which RFC 9535 §2.3.3 puts at ±(2^53 − 1) — wider than Int32, and wider than any array that can exist, which is why an index past the end selects nothing rather than being refused.
WildcardEvery element of an array, or every member of an object.
Slice(start~ : Int64?, end~ : Int64?, step~ : Int64?)A range of an array. None means "from the beginning", "to the end", or "by one" — the three defaults RFC 9535 §2.3.4.2 gives the three parts.
Filter(Test)The nodes for which the expression holds.
pub(all) enum TestWhat a filter asks.
Or(Test, Test)Either side holds.
And(Test, Test)Both sides hold.
Not(Test)The test does not hold.
Exists(Operand)Whether the query selects anything at all.
Compare(Operand, Op, Operand)Whether two values compare as the operator says.
One tree, one document: JCS exists so that two parties can sign the same bytes. Nothing here is a knob, because a canonical form with options is not canonical — the numbers are ECMAScript's, the member order is UTF-16 code unit order, and a number that is not finite is refused rather than spelled some way we invented.
pub fn dump(Json) -> Bytes raise @moonjson.RefusedThe same, as the UTF-8 bytes a signature is taken over.
pub fn dumps(Json) -> String raise @moonjson.RefusedThe canonical form of a tree, as RFC 8785 writes it.
Members are ordered by their names compared as UTF-16 code units — not as code points, which differ for anything above U+FFFF — numbers are written the way ECMAScript writes them, and nothing is escaped that does not have to be. Two trees that hold the same values therefore come out as the same bytes, which is the whole point: a signature over these bytes is a signature over the document.
A number that is not finite cannot be written, because the RFC's number serialisation is ECMAScript's and ECMAScript has no syntax for it. It is refused rather than replaced with null or 0, either of which would sign something the document does not say.
CBOR is JSON's data model with the corners filled in: byte strings, integers that a double cannot hold, tags, and a distinction between false and undefined. It writes itself into a tree the rest of this family already understands, and what the tree cannot hold outright is carried in the markers documented on load — so a CBOR document can be read here and written as JSON, and a JSON document can be written here and read there.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as CBOR.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedWhat a tree becomes when a CBOR value has no JSON counterpart.
JSON has no byte strings, no tags, no undefined and no map keys that are not strings, so those four are carried as objects with one reserved member each. They are the same markers every other binary package in this family uses, so a value read from CBOR and written to MessagePack keeps its bytes:
| CBOR | tree |
|---|---|
h'0102' | {"$bytes": "0102"} — lowercase hex, no separators |
1(1363896240) | {"$tag": 1, "value": 1363896240} |
undefined | {"$undefined": true} |
simple(16) | {"$simple": 16} |
{1: 2} | {"$map": [[1, 2]]} — any map with a key that is not a string |
A map whose keys are all strings is written as a plain object, and a plain object is read back as a map. A document that means {"$bytes": "00"} as data has to be written in the $map form, because the marker is reserved — the same bargain MongoDB's Extended JSON makes, and for the same reason: a tree with one object type needs one reserved name.
Writing also accepts these markers: an object with exactly one $bytes member becomes a byte string, and so on for the rest. What was read from CBOR is therefore written back as the same CBOR, and MessagePack's byte strings, BSON's binary and CBOR's are one thing in the tree.
pub let policy : PolicyThe defaults: deterministic, five hundred levels deep.
pub(all) struct PolicyHow CBOR is read and written.
Two knobs, both of them things the RFC leaves open rather than things we invented: whether to write the deterministic form, and how deep a document may nest before it is refused.
deterministic : BoolWrite the deterministic encoding of RFC 8949 §4.2.3: the shortest form for every length and every number, floats in the shortest width that reads back as the same value, and map keys in bytewise order of their encodings.
It is on by default because that is what the RFC §4.2 recommends and what a signature or a hash over the encoding needs. Turning it off still writes valid CBOR — every float becomes a double and map keys keep the order the tree has — which is what a reader that does not care about byte identity may prefer, because it is cheaper.
depth : Int32How deep nesting may go. The same five hundred the text reader allows, and for the same reason: a document of a hundred thousand open brackets is a denial of service rather than a document.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(deterministic? : Bool, depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the defaults.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
MessagePack is CBOR's older cousin: the same data model, a different header, and no tags. Two things it has that JSON has not are a byte string and an extension type, and both are carried in the markers documented on load — the same markers CBOR uses for bytes, so a document moved from one to the other keeps its bytes.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as MessagePack.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedRead one MessagePack document.
What JSON cannot hold comes back as the family's markers:
| MessagePack | tree |
|---|---|
bin | {"$bytes": "0102"} — lowercase hex |
ext | {"$ext": 1, "data": "0102"} — the type as a number |
| an integer above 2^53 | a number that keeps its digits as text |
The same markers are read back as extensions and byte strings, so a document survives a round trip through this package.
pub let policy : PolicyThe defaults: doubles, five hundred levels.
pub(all) enum FloatsHow wide a float is written.
Shortest32 bits when the value comes back unchanged, 64 when it does not.
DoubleAlways 64 bits.
pub fn Floats::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Floats::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Floats::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
pub(all) struct PolicyHow MessagePack is read and written.
floats : FloatsMessagePack's reference implementation writes every float as a double unless it is told otherwise, so that is the default: a 32-bit float that reads back the same is smaller but is not what the other side expects to see.
depth : Int32How deep nesting may go before the document is refused.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(floats? : Floats, depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the defaults.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
BSON is what MongoDB stores and puts on the wire: little-endian, length-first, and richer than JSON by eleven types. Every one of them has a marker here, so nothing it carries is lost — an ObjectId stays twelve bytes, a decimal128 stays sixteen, and a timestamp stays two 32-bit halves.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as a BSON document.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedRead one BSON document.
A BSON document is always an object at the top, and the types JSON has no syntax for come back as markers:
| BSON | tree |
|---|---|
| binary, subtype 0 | {"$bytes": "0102"} |
| binary, any other subtype | {"$binary": {"kind": 4, "data": "0102"}} |
| ObjectId | {"$oid": "507f1f77bcf86cd799439011"} |
| UTC datetime | {"$date": 1356350400000} — milliseconds |
| regular expression | {"$regex": "^a", "options": "i"} |
| DBPointer | {"$dbpointer": {"ns": "db.c", "oid": "…"}} |
| JavaScript | {"$code": "…"} |
| JavaScript with scope | {"$code": "…", "$scope": {…}} |
| symbol | {"$symbol": "…"} |
| timestamp | {"$timestamp": {"t": 1, "i": 2}} |
| decimal128 | {"$decimal128": "…32 hex digits…"} |
| undefined | {"$undefined": true} |
| MinKey / MaxKey | {"$minKey": true} / {"$maxKey": true} |
Writing reads the same markers back, so a document that goes through the tree comes out as it went in. A marker is a reserved name: an object whose only member is $oid is an ObjectId, not a document that happens to have that member.
pub let policy : PolicyThe defaults: five hundred levels.
pub(all) struct PolicyHow BSON is read and written.
depth : Int32How deep a document may nest. BSON documents nest inside documents, and a chain of them is as good a way to exhaust a stack as a chain of brackets.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBSON writes int32 and int64 as two types and the tree holds one number, so a number is written in the narrowest width that holds it — the same choice the reference implementations make when they are handed a language integer rather than a typed one. The value is the same; only the width differs, and int32 is what MongoDB itself uses below two billion.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
UBJSON is JSON's data model exactly: null, true, false, numbers, strings, arrays and objects, with no byte strings, no tags, no undefined and no non-string map keys. A UBJSON document reads into the same tree the rest of this family already understands, and what the tree cannot hold outright is carried in the markers documented on load — written, where UBJSON allows, as the closest plain thing.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as UBJSON, in the simple (no length-prefix) form.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedWhat a tree becomes when a UBJSON value has no JSON counterpart.
UBJSON *is* JSON's data model — null, true, false, numbers, strings, arrays and objects, with no byte strings, no tags, no undefined and no non-string map keys — so most of the markers the rest of this family uses have no UBJSON form. They are documented here, and what UBJSON cannot carry is either written as the closest plain thing or, for bytes, as an array:
| UBJSON | tree |
|---|---|
blob | {"$bytes": "0102"} — written as a UBJSON array [U 01, U 02] of uint8; reading that array back yields a plain array, not the marker — UBJSON has no byte string, so the marker is one-way (documented, not round-tripped) |
1(1363896240) | {"$tag": 1, "value": 1363896240} — written as a plain object with those members |
{1: 2} | {"$map": [[1, 2]]} — written as a plain object |
simple(16) | {"$simple": 16} — written as a plain object |
undefined | {"$undefined": true} — written as a plain object |
A document that means {"$bytes": "00"} as data therefore has to be written in the $map form, because the marker is reserved — the same bargain every other package in this family makes, here reduced to a single marker that has only a one-way encoding. The other four markers ($tag, $map, $simple, $undefined) are written as plain objects with those member names, because UBJSON has no native form for them at all.
Writing also accepts these markers: an object with exactly one $bytes member becomes a UBJSON array of uint8, so a byte string read elsewhere and written here is carried as closely as UBJSON allows. What a real UBJSON document needs — numbers, strings, arrays, objects — needs no marker in the tree at all, and the non-JSON markers above are only round-tripped between the other packages of this family, never through UBJSON itself.
Encoding is the canonical, length-free "simple" form of the spec: containers are opened with [/{ and closed with ]/} with a per-element type marker, never the optimized # (count) or $ (type) prefixes. It is spec-based and is not byte-validated against an external UBJSON library, because none is available in this workspace.
pub let policy : PolicyThe default: five hundred levels deep.
pub(all) struct PolicyHow UBJSON is read and written.
The one knob the format leaves open is how deep a document may nest before it is refused, because a document of a hundred thousand open brackets is a denial of service rather than a document.
depth : Int32How deep nesting may go. The same five hundred the text reader allows, and for the same reason: a document of a hundred thousand open brackets is a denial of service rather than a document.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the default.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
Smile is JSON's data model with one addition, a byte string, written as a 4-byte :)\n\0 header, then one value, then a 0x00 end marker. A Smile blob reads into the same tree the rest of this family already understands, and what the tree cannot hold outright is carried in the markers documented on load — so a blob read here is written as a blob again, and the byte strings of CBOR, MessagePack and BSON are the one $bytes in the tree.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as Smile.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedWhat a tree becomes when a Smile value has no JSON counterpart.
JSON has no byte string and no notion of a tag, undefined or a non-string key, so those four are carried as objects with one reserved member each, the same markers every other binary package in this family uses, so a value read from Smile and written to CBOR keeps its meaning:
| Smile | tree |
|---|---|
binary | {"$bytes": "0102"} — lowercase hex, no separators |
tag(n, v) | {"$tag": n, "value": v} |
{k: v} with a non-string key | {"$map": [[k, v], ...]} |
simple(n) | {"$simple": n} |
undefined | {"$undefined": true} |
Of these, only $bytes has a native Smile form: Smile's binary value. So the encoder turns a single-member {"$bytes": "hex"} object into a Smile binary, and the decoder turns a Smile binary back into {"$bytes": "hex"}. The remaining four markers ($tag, $map, $simple, $undefined) have no native Smile encoding; they are written as the plain objects they are and read back as plain objects, so a tree that already carries one round-trips without it being mistaken for something else.
pub let policy : PolicyThe default: five hundred levels deep.
pub(all) struct PolicyHow Smile is read and written.
The byte layout itself is fixed by the spec; the one knob is how deep a document may nest before it is refused, because a document of a hundred thousand open brackets is a denial of service rather than a document.
depth : Int32The deepest nesting accepted, in levels.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the default.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
bjson — "Binary JSON".
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as bjson.
A value of any shape is accepted at the top level. The types JSON has no syntax for are written through the $bytes and $undefined markers documented on load, so a tree read from another binary package survives the round trip.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedRead one bjson document from its bytes.
bjson is binary JSON, so the one type JSON has no syntax for — raw bytes — comes back through the same markers every binary package in this family uses, so a value read here and written to CBOR or BSON keeps its bytes:
| bjson | tree |
|---|---|
0x0B–0x0D binary | {"$bytes": "0102"} — lowercase hex, no separators |
0x10 undefined | {"$undefined": true} |
(The full marker set used across the family is $bytes, $tag, $map, $simple, $undefined; this minimal reader accepts $bytes and $undefined, which are the ones a binary-JSON round trip needs.)
pub let policy : PolicyThe default: five hundred levels deep.
pub(all) struct PolicyHow bjson is read and written.
depth : Int32How deep nesting may go, shared with the rest of the family. A document of a hundred thousand open containers is a denial of service rather than a document, so the same five hundred the text reader allows is the default.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBuild a policy naming the depth that differs from the default.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
KRSON is a real, schema-first binary serialization format. The authoritative SPEC.md the project references was not publicly retrievable at the time of writing and the authors state the wire format "may still change between minor versions", so this package implements the self-describing *schemaless* subset (magic "KR", a string interning table, varint + zigzag integers, big-endian float64) as a genuine encoder/decoder. See krson.mbt for the full design note and fidelity warning.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a Json tree as a KRSON schemaless document.
See load for the design note and the fidelity warning. The writer builds the string-interning table from every object key and string value in the tree, writes the KR header with a zero schema id, the table, then the value tree using the tags below. A value the format cannot carry as JSON — there is none in the JSON-native model — would raise @moonjson.Unwritable.
| tag | tree |
|---|---|
0x00 | null |
0x01 + 0/1 | true / false |
0x02 + zigzag varint | an integer |
0x03 + 8 bytes (big-endian) | a float64 |
0x04 + varint | a string, by index into the string table |
0x05 + varint + items | an array |
0x06 + varint + (key-index, value) pairs | an object |
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedRead a KRSON document and turn it into a Json tree.
KRSON is a real, schema-first binary serialization format (Rust core krson-core, JS SDK krson-js). Its schema-first form carries a 4-byte schema id and needs the originating schema runtime to interpret — a thing the shared Json tree here cannot host, and one that is not portable on its own. This package therefore implements the self-describing schemaless record, which the SDK also emits (encode/decode with no schema): magic KR, a version and flags byte, a schema id of 0, a string-interning table (every object key and string value is written once and referred to by index), then the value tree.
WARNING ON FIDELITY — the authoritative SPEC.md referenced by the KRSON project was not publicly retrievable at the time of writing, and the authors state the wire format "may still change between minor versions". This codec follows the primitives the SDK documents (magic KR, a length-prefixed string table, varint + zigzag integers, big-endian float64, and the value tags below). It is a genuine, self-consistent encoder/decoder of that subset; byte-exact interop with upstream KRSON is best-effort.
The value model is JSON-native, so this reader never produces the $bytes, $tag, $map, $simple or $undefined markers the other binary packages in this family use: a KRSON document has no byte strings, tags, maps with non-string keys, undefined or simple values to carry.
pub let policy : PolicyThe defaults: keys in source order, five hundred levels deep.
pub(all) struct PolicyHow KRSON is read and written.
Two knobs, both things the format leaves to the caller rather than things we invented: whether object members are written in sorted key order, and how deep a document may nest before it is refused.
deterministic : BoolWrite object members in bytewise order of their key. KRSON is a self-describing format, so the order never changes what is read back, but a caller that wants a stable byte image (for a hash or a signature) turns this on.
depth : Int32How deep nesting may go. The same five hundred the text reader allows, and for the same reason: a document of a hundred thousand open brackets is a denial of service rather than a document.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(deterministic? : Bool, depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the defaults.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
A compact binary encoding of the JSON data model: a 5-byte header, then each value as a one-byte descriptor whose low 3 bits are the type and whose high 5 bits hold a short length, with a varint escape for longer ones. Numbers are kept as IEEE-754 bits and a byte string as the reserved {"$bytes": "…"}.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as CBD.
The dictionary is built in the order keys are first met, matching the specification's "order of first appearance".
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedRead one CBD document.
The header is magic 0xCB 0xD1, version 0x01, then the dictionary size. A byte string has no type of its own in CBD v0.1.0, so it stays an ordinary object {"$bytes": "…"} — the marker every package in this family shares — and a trip through CBD leaves it exactly as it was.
pub let policy : PolicyThe default: five hundred levels deep.
pub(all) struct PolicyHow CBD is read and written.
The byte layout is fixed; the one knob is how deep a document may nest before it is refused, because a document of a hundred thousand open brackets is a denial of service rather than a document.
depth : Int32The deepest nesting accepted, in levels.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the default.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
jsonb is JSON's data model with one addition, a byte string, so a SQLite blob reads into the same tree the rest of this family already understands, and what the tree cannot hold outright is carried in the markers documented on load.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as SQLite jsonb.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedWhat a tree becomes when a SQLite jsonb value has no JSON counterpart.
JSON has no byte string, so a SQLite blob is carried as an object with one reserved member:
| jsonb | tree |
|---|---|
blob | {"$bytes": "0102"} — lowercase hex, no separators |
SQLite jsonb is JSON's data model plus that one addition, so a blob reads into the same tree the rest of this family already understands, and what the tree cannot hold outright is carried in the markers documented here. A blob read here is written as a blob again, and the byte strings of CBOR, MessagePack and BSON are the one $bytes in the tree.
An object that happens to have a single $bytes member as its *data* has to be written through the $map form, because the marker is reserved — the same bargain every other package in this family makes.
pub let policy : PolicyThe default: five hundred levels deep.
pub(all) struct PolicyHow SQLite jsonb is read and written.
The byte layout itself is fixed by SQLite; the one knob is how deep a document may nest before it is refused, because a document of a hundred thousand open brackets is a denial of service rather than a document.
depth : Int32The deepest nesting accepted, in levels.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the default.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
The layout follows PostgreSQL's on-disk jsonb shape (src/include/utils/jsonb.h): a length/type header word, an element-count word, a JEntry per element, and a data region. Numbers are kept as IEEE-754 bits so a tree round-trips exactly.
pub fn dump(Json, policy? : Policy) -> Bytes raise @moonjson.RefusedWrite a tree as PostgreSQL jsonb.
pub fn load(BytesView, policy? : Policy) -> Json raise @moonjson.RefusedWhat a tree becomes when a jsonb value has no JSON counterpart.
| jsonb | tree |
|---|---|
blob | {"$bytes": "0102"} — lowercase hex, no separators |
A byte string survives a trip through this format the same way it does through SQLite jsonb, so the byte strings of CBOR, MessagePack and BSON are one $bytes in the tree whatever format carried them. A number is kept as PostgreSQL's NumericVar, an exact base-10000 decimal, so it round-trips digit for digit.
pub let policy : PolicyThe default: five hundred levels deep.
pub(all) struct PolicyHow PostgreSQL jsonb is read and written.
The byte layout is fixed; the one knob is how deep a document may nest before it is refused, because a document of a hundred thousand open brackets is a denial of service rather than a document.
depth : Int32The deepest nesting accepted, in levels.
pub fn Policy::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::new(depth? : Int32) -> SelfBuild a policy by naming the parts that differ from the default.
pub fn Policy::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Policy::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
A JSON superset that wraps a value in a {"json": <value>, "meta": {...}} envelope carrying the type of each nested value — Date, BigInt, Map, Set, and so on — so the receiver can rebuild the JavaScript objects the JSON wire form cannot name.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
Writes the tree — including any superjson json/meta envelope — as ordinary JSON, delegating to @moonjson.dump.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
A superjson payload is JSON, so writing one is just ordinary JSON output (delegating to @moonjson.dumps). The envelope — json and meta — is emitted exactly as read. indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
Reads the superjson envelope as ordinary JSON, preserving both the json payload and the meta type table verbatim. See loads for why this package does not itself reconstruct JavaScript types: the Json tree has none to reconstruct into, and the wire form is plain JSON regardless.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one superjson document.
Superjson's wire form is plain JSON shaped as {"json": <value>, "meta": { "values": { "<path>": "<type>" }, ... }}. The Json tree in this library has no Date, BigInt, Map or Set type, so there is nothing here to rebuild a JavaScript value into — and inventing one would not be faithful, because the whole point of the envelope is that those types live in JavaScript, not on the wire. The honest thing to do is therefore the simplest: parse the envelope as an ordinary JSON object (delegating to @moonjson.loads) and keep json and meta verbatim. The typed values survive as the plain JSON they decode to; the type table survives as the preserved meta object.
Full JavaScript-type reconstruction is out of scope for this Json tree. A caller that needs real Date/BigInt/Map/Set objects must rebuild them from the meta on the JavaScript side, where those types exist.
An IETF Internet-Draft that adds an optional name:type tag to a JSON value by wrapping it in an object whose single member carries the tag as its key (e.g. {"paris:point": [2.35, 48.85]}). Because that wrapping is itself valid JSON, this package does not keep a separate tree: it reads and writes the value with the shared Json tree, leaving name:type keys exactly as written. See ntv.mbt for the full design note.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
See dumps for the design note: a tagged value is written back as the JSON object with its name:type key intact.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean — what is sometimes called ensure_ascii, except that it is off by default here, because a UTF-8 body is the normal case now. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
A JSON-NTV value leaves here still a JSON object with a name:type key, because this delegates to @moonjson.dumps and does not rewrite keys.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
See loads for the design note: a JSON-NTV document is valid JSON, so the bytes are read with the shared reader and any name:type key is preserved as a plain object key.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one JSON-NTV document.
DESIGN — JSON-NTV is JSON. A tagged value such as {"paris:point": [2.35, 48.85]} is a perfectly ordinary JSON object with one member whose key happens to read name:type. There is therefore no separate tree to parse into: this call delegates to @moonjson.loads, so the name:type key is kept verbatim as the member key it is.
LIMITATION — because the shared Json tree has no notion of a typed name, the name and type parts of the key are not split apart or interpreted; they survive only as a plain object key. A reader that needs the name or the type must read the key string and divide it on the last :. This is the faithful and honest reading: a tagged value is valid JSON, and nothing here pretends otherwise.
"rjson" is most often a shorthand for the Ruby json standard-library, or the rjson gem built on it. Both serialize Ruby objects to standard JSON and parse standard JSON back; there is no separate "rjson" textual grammar. This package therefore delegates to @moonjson — an rjson payload is exactly JSON.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
Writes the tree as ordinary JSON, delegating to @moonjson.dump; an rjson payload is standard JSON on the wire.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as standard JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean. sort writes members in order of name instead of the order they were read in, which is what a diff wants. An rjson payload IS standard JSON, so this is ordinary output.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
Reads rjson as ordinary JSON, delegating to @moonjson.load; an rjson payload is standard JSON regardless of how it is transported.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one rjson document.
The Ruby json library emits and accepts standard JSON (RFC 8259), so this delegates to @moonjson.loads; an rjson payload is ordinary JSON.
json11 is a tiny C++11 library providing JSON parsing and serialization; its output is standard JSON (RFC 8259) — the library adds no syntax of its own. This package therefore delegates to @moonjson — a json11 payload is exactly JSON.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
Writes the tree as ordinary JSON, delegating to @moonjson.dump; a json11 payload is standard JSON on the wire.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as standard JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean. sort writes members in order of name instead of the order they were read in, which is what a diff wants. A json11 payload IS standard JSON, so this is ordinary output.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
Reads json11 as ordinary JSON, delegating to @moonjson.load; a json11 payload is standard JSON regardless of how it is transported.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one json11 document.
json11 emits and parses standard JSON, so this delegates to @moonjson.loads; a json11 payload is ordinary JSON.
JSON-Z is a superset of JSON (and of JSON5) that adds comments, single and backtick quotes, unquoted keys, trailing commas, hex/octal/binary numbers, +-prefixed and Infinity/-Infinity/NaN numbers, and more. This package reads and writes the JSON-representable subset by delegating to @moonjson's relaxed reader; the JSON-Z-exclusive numeric suffixes (BigInt n, decimal m, Decimal128 d), sparse arrays, undefined, and extended typed containers cannot be held by the shared Json tree and are out of scope.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
Writes the tree as ordinary JSON, delegating to @moonjson.dump; a value read from JSON-Z's representable subset is written back as JSON.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean. sort writes members in order of name instead of the order they were read in, which is what a diff wants. The JSON-Z representable subset round-trips as JSON here; the JSON-Z-exclusive numeric and container extensions cannot be reconstructed.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
Reads JSON-Z as ordinary JSON-Z (the JSON5-compatible subset), delegating to @moonjson.load; see loads for the JSON-Z features this package keeps and the ones it cannot represent in a Json tree.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one JSON-Z document.
JSON-Z shares its JSON5-compatible surface — comments, single and backtick quotes, unquoted object keys, trailing commas, hexadecimal and signed numbers, Infinity and NaN — with the relaxed reader, so those are parsed here by delegating to @moonjson.loads with flavor = relaxed.
LIMITATION — JSON-Z adds values the shared Json tree cannot hold: BigInt (the n suffix), arbitrary-precision decimals (m) and IEEE-754 Decimal128 (d), sparse arrays, undefined, and extended typed containers (Date, Map, Set, RegExp, Uint8Array). These are out of scope for a Json tree that has none of those types, exactly as superjson and ntv are out of scope for the types they cannot rebuild. A document that uses them is not accepted here.
ZSON is a PostgreSQL extension for transparent JSONB compression. It is a server-side, dictionary-trained compression of JSONB; it is NOT a portable wire format — the stored bytes depend on a per-database trained dictionary, so no standalone decoder is possible. What travels between systems is JSONB, i.e. JSON. This package therefore delegates to the shared JSON reader/writer; see zson.mbt for the full design note. (Do not confuse it with ZJSON, the Zed JSON encoding, which lives in moonbitstack/moonjson/zjson.)
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
See dumps for the design note: the output is plain JSON ready to be stored into (or sent to) a ZSON column, which then compresses it with its trained dictionary.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean — what is sometimes called ensure_ascii, except that it is off by default here, because a UTF-8 body is the normal case now. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
The result is the JSON a ZSON column accepts: the compression step that follows is PostgreSQL's, not this package's.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
See loads for the design note: a ZSON value is jsonb — that is, JSON — so the bytes are read with the shared reader and the compression itself is out of scope for a portable library.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one ZSON document.
ZSON is a PostgreSQL extension that compresses jsonb transparently, using a shared dictionary trained on the data a deployment actually stores. That makes it a *server-side* compression, not a portable wire format: the bytes on disk depend on a per-database dictionary that is not part of the document, so no standalone decoder can turn them back into a value. What travels between systems — the jsonb a column holds, the text a client sends or receives — is ordinary JSON, and that is the only part this library can faithfully handle. The honest reading is therefore a delegation: parse the value as JSON with the shared reader. A caller that needs real ZSON storage must use PostgreSQL with the extension installed; this package only understands the JSON a ZSON column carries.
(Do not confuse this with *ZJSON*, the Zed JSON encoding — a different format, implemented in moonbitstack/moonjson/zjson.)
ZJSON is the JSON encoding of the Zed data model: a stream of JSON objects, each of the form {"type": <zed-type>, "value": <json-value>}. It is valid JSON — the Zed type lives in a member alongside the value — so this package delegates to the shared JSON reader/writer and keeps the type/value members verbatim; see zjson.mbt for the full design note. (Do not confuse it with ZSON, the PostgreSQL JSONB-compression extension, which lives in moonbitstack/moonjson/zson.)
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
See dumps for the design note: the type/value envelope is written back as the JSON object it is, delegating to @moonjson.dump.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
indent of zero is the compact form. ascii escapes everything above U+007E, for a transport that is not eight-bit clean — what is sometimes called ensure_ascii, except that it is off by default here, because a UTF-8 body is the normal case now. sort writes members in order of name instead of the order they were read in, which is what a diff wants.
A ZJSON value leaves here still a JSON object carrying type and value, because this delegates to @moonjson.dumps and does not rewrite members.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedThe same, from the UTF-8 bytes a body arrives as.
See loads for the design note: a ZJSON document is valid JSON, so the bytes are read with the shared reader and the type/value members are kept exactly as written.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one ZJSON document.
ZJSON is the JSON encoding of the Zed data model: a stream of JSON objects, each of the form {"type": <zed-type>, "value": <json-value>}, where type describes the rich Zed type (a primitive name like "int64", or a complex type built from record/array/map/union/enum/error/ref nodes) and value is the value rendered in plain JSON. It is, underneath, valid JSON — the type lives in a member alongside the value.
That is the same situation as superjson's envelope: the Json tree in this library has no Zed type system to rebuild into, so the faithful, honest reading is a delegation. This call parses the document as ordinary JSON with the shared reader, keeping type and value verbatim. A caller that needs real Zed typed values must interpret type against the Zed type model in a Zed-aware client — there is nothing here to lose by reading it as JSON, only the type annotation, which survives as the preserved type member.
(Do not confuse this with *ZSON*, the PostgreSQL JSONB-compression extension — a different format, implemented in moonbitstack/moonjson/zson.)
A line- and indentation-based dialect of JSON: no braces, no commas, keys and values separated by colons and newlines, nesting shown by indentation, arrays by - bullets. The reader parses it into the shared Json tree; the writer delegates to @moonjson, because CSON has no canonical text form and what it does have does not survive a round trip, so the only faithful rendering is the strict JSON @moonjson already knows how to write.
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
Like dumps, the text produced is strict JSON written by @moonjson; CSON itself has no faithful textual rendering.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON.
CSON has no canonical text form — nothing the writer could print would be CSON a reader recognises as the same document — so this delegates to @moonjson.dumps, which already knows how to emit strict JSON for any Json.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedRead one CSON document from the UTF-8 bytes a body arrives as.
A leading byte-order mark is skipped by default. If the bytes are not valid UTF-8 the document is refused rather than silently mangled.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one CSON document.
The root is an object whose members sit at the first indentation level (or an array, if the first line is a - bullet). CSON is indentation-based, so the document is first reduced to structural lines and then walked recursively; what comes back is the same Json tree every dialect in this family reads into.
Hjson — a user interface for JSON: https://hjson.github.io/
pub fn dump(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> BytesThe same, as the UTF-8 bytes a body goes out as.
pub fn dumps(Json, indent? : Int32, ascii? : Bool, sort? : Bool) -> StringWrite a tree as strict JSON, whatever dialect it was read from.
Hjson has no canonical text form of its own — the same tree can be written many ways — so the writer emits strict JSON by delegating to @moonjson.
pub fn load(BytesView, flavor? : @moonjson.Flavor, bom? : Bool) -> Json raise @moonjson.RefusedRead one Hjson document from the UTF-8 bytes a body arrives as.
A leading byte-order mark is skipped by default. Decoding that fails is a Bad refusal.
pub fn loads(StringView, flavor? : @moonjson.Flavor) -> Json raise @moonjson.RefusedRead one Hjson document from text.
Hjson is a superset of JSON as far as the tree is concerned, so whatever comes back here can be written out again as strict JSON with dumps. The flavor argument is accepted for the family's common signature; Hjson is always read leniently.
Streaming JSON sink — newline-delimited JSON (NDJSON): https://github.com/ndjson/ndjson-spec
pub struct SinkA sink that accumulates a sequence of JSON documents, one to a line.
The documents are buffered in memory; the value of the type is that it is written one document at a time, so a caller that produces them lazily never builds the whole array first.
buffer : StringBuilderThe bytes written so far.
pub fn Sink::length(Self) -> Int32How many documents have been written.
pub fn Sink::new() -> SelfA new, empty sink.
pub fn Sink::to_bytes(Self) -> BytesEverything written so far, as the UTF-8 bytes a body goes out as.
pub fn Sink::to_string(Self) -> StringEverything written so far, as text.
pub fn Sink::write(Self, Json) -> UnitWrite one document, in the compact form, followed by a line feed.
Every record ends in its own line feed, including the last, which is what makes the output appendable — the format's whole purpose.
Streaming JSON source — newline-delimited JSON (NDJSON): https://github.com/ndjson/ndjson-spec
pub struct SourceA source that hands back a sequence of JSON documents, one at a time.
text : StringViewThe stream as decoded text.
pub fn Source::is_done(Self) -> BoolWhether the stream has been fully read.
pub fn Source::length(Self) -> Int32How many documents have been read so far.
pub fn Source::new(BytesView) -> Self raise @moonjson.RefusedA source over the UTF-8 bytes of a stream.
A leading byte-order mark is skipped. Bytes that are not UTF-8 are a BadUtf8 refusal at the start of the stream.
pub fn Source::read(Self) -> Json? raise @moonjson.RefusedThe next document, or None once the stream is exhausted.
A blank line is skipped. A line that is not a value is refused with the position of the failure in that line.
The core writer already writes an indented form; this package adds the knobs a formatter wants — tabs instead of spaces, CRLF line endings — and the text-to-text entry point a command-line tool needs, which is what python -m json.tool does: read a document, write it back in the chosen shape. It is a package of its own rather than part of the core because the core's dumps signature is depended on everywhere and must stay stable, and because a build that never imports this one never compiles it.
pub fn format(Json, style? : Format) -> StringWrite a tree in the chosen shape.
The core writer does the work; this only bends its output where the core's signature cannot — a tab for each level, a carriage return before each line feed. A JSON string never contains a raw line feed (it is escaped), so rewriting line endings cannot touch a value.
pub let pretty : FormatThe default: two spaces a level, LF line endings, the order the tree keeps.
pub fn reformat(StringView, style? : Format) -> String raise @moonjson.RefusedRead a document and write it back in the chosen shape — the text-to-text entry point, what python -m json.tool does.
pub fn reformat_bytes(BytesView, style? : Format) -> Bytes raise @moonjson.RefusedThe same, from the UTF-8 bytes of a document.
pub(all) struct FormatHow to lay a document out.
The defaults are the ones a formatter is usually asked for — two spaces a level, LF line endings — and every field can be changed independently.
indent : Int32Spaces per level. Zero writes the whole document on one line.
tabs : BoolOne tab per level instead of spaces; indent is then ignored.
crlf : BoolA carriage return before each line feed, for a file Windows reads.
sort : BoolSort members by name, code point by code point.
ascii : BoolEscape everything above U+007F so the output is plain ASCII.
pub fn Format::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Format::new(indent? : Int32, tabs? : Bool, crlf? : Bool, sort? : Bool, ascii? : Bool) -> SelfBuild a format by naming the parts that differ from the default.
pub fn Format::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Format::to_repr(Self) -> @debug.ReprWritten by derive(Debug).