The JSON family for MoonBit.
let config = @json.loads("{\"host\":\"localhost\",\"port\":8080}")
@json.dumps(config) // {"host":"localhost","port":8080}
@json.dumps(config, indent=2) // over several lines
// The same document, written the way a settings file is written.
@jsonc.loads("{\"port\": 8080, // the one we bound\n}")
// One place in a document, named (RFC 6901).
@pointer.get(config, "/host") // "localhost"| text → tree | bytes → tree | tree → text | tree → bytes | |
|---|---|---|---|---|
| one document | loads | load | dumps | dump |
| a stream of them | loads_all | load_all | dumps_all | dump_all |
// Read a document, change one field, write it back.
let cfg = @jsonc.loads(text[:])
let cfg = @pointer.set(cfg, "/spec/replicas", Json::number(5))
@json.dumps(cfg, indent=2)| Package | What | Specification |
|---|---|---|
| json | JSON, exactly | RFC 8259, ECMA-404 |
| jsonc | JSON with comments and trailing commas | what VS Code accepts |
| json5 | JSON5: unquoted names, single quotes, hexadecimal, Infinity | spec.json5.org |
| lines | JSON Lines / NDJSON: one document to a line | jsonlines.org |
| pointer | JSON Pointer | RFC 6901 |
| patch | JSON Patch: add, remove, replace, move, copy, test | RFC 6902 |
| merge | JSON Merge Patch | RFC 7396 |
| path | JSONPath: a query language over the tree, filters and all | RFC 9535 |
| jcs | Canonical JSON: one document, one sequence of bytes | RFC 8785 |
| cbor | CBOR, the binary encoding JSON's own working group wrote | RFC 8949 |
| msgpack | MessagePack | msgpack.org |
| bson | BSON, what MongoDB stores and sends | bsonspec.org |
| moonjson | The scanner the dialects share, the writer, and Refused | — |
| Value | Marker |
|---|---|
| a byte string | {"$bytes": "0102"} — lowercase hex, no separators |
| a tag or an extension type | {"$tag": 1, "value": …}, {"$ext": 1, "data": "0102"} |
| undefined | {"$undefined": true} |
| a map whose keys are not strings | {"$map": [[1, 2]]} |
let doc = @json5.loads("{host:'localhost',port:8080,tls:false}")
let body = @cbor.dump(doc) // the same tree, as bytes
let doc = @cbor.load(body) // and back
@json.dumps(doc) // {"host":"localhost","port":8080,"tls":false}@json.loads(text) // strict JSON
@jsonc.loads(text) // the same, plus comments
@json.loads(text, flavor=@moonjson.Flavor::new(depth=100))
@json.loads(text, flavor={ ..@moonjson.strict, duplicates: Reject })@json.dumps(value) // compact
@json.dumps(value, indent=2) // over several lines
@json.dumps(value, ascii=true) // escape everything above U+007E
@json.dumps(value, sort=true) // members in order of name| Setting | Default | Why that one |
|---|---|---|
| depth | 500 | A hundred thousand open brackets is a denial of service, not a document |
| surrogates | refuse | The case is implementation-defined, so the safer side is the default: a lone half cannot be written back out |
| duplicates | Last | The behaviour most readers already have |
| bom | skipped | RFC 8259 §8.1 forbids writing one and says nothing about reading one; refusing would reject half of what Windows tooling produces |
| ascii | off | A UTF-8 body is the normal case now |
| sort | off | The order a document was written in is information; a diff is the reason to discard it, and a diff can ask |
| CBOR deterministic | on | RFC 8949 §4.2 recommends it, and a hash or a signature over an encoding needs it. Off still writes valid CBOR, only longer |
| CBOR depth | 500 | The same as the text reader, for the same reason |
| MessagePack floats | Double | No precision is lost unless asked |
| BSON depth | 500 | Documents nest inside documents exactly as brackets nest inside brackets |
// One field, and nothing else changes.
let cfg = @pointer.set(cfg, "/spec/replicas", Json::number(5))
// Six operations, applied in order — or, if one fails, not at all.
let ops = @patch.loads(
"[{\"op\":\"test\",\"path\":\"/spec/replicas\",\"value\":1},{\"op\":\"replace\",\"path\":\"/spec/replicas\",\"value\":5}]"
)
@patch.apply(cfg, ops)
// A patch that looks like the document: a member that is not there is added,
// one that is there is replaced, and a null takes one away.
@merge.merge(cfg, @json.loads("{\"spec\":{\"replicas\":5,\"image\":null}}"))try @json.loads(source) catch {
e => match e.at() {
Some(spot) => println("line \{spot.line}, column \{spot.column}")
None => println(e) // a pointer or a patch, which has a path instead
}
}moon add moonbitstack/moonjsonpub(all) suberror Refused {
Unexpected(At, Char)
Truncated(At)
BadEscape(At)
BadNumber(At)
Trailing(At)
TooDeep(At)
BadUtf8(At)
Repeated(At, String)
Short(at~ : Int)
Extra(at~ : Int)
Bad(at~ : Int, why~ : String)
Unwritable(why~ : String)
Syntax(path~ : String, at~ : Int)
Missing(path~ : String, token~ : String)
NotContainer(path~ : String, token~ : String)
BadIndex(path~ : String, token~ : String)
Document(why~ : String)
Operation(at~ : Int, why~ : String)
TestFailed(path~ : String)
IntoChild(from~ : String, path~ : String)
Root(path~ : String)
} derive(Eq, Debug)pub(all) struct Flavor {
comments : Bool
trailing_commas : Bool
unquoted_keys : Bool
single_quotes : Bool
extra_numbers : Bool
extra_escapes : Bool
extra_space : Bool
depth : Int
surrogates : Bool
duplicates : Duplicates
} derive(Eq, Debug)fn Flavor::new(comments? : Bool, trailing_commas? : Bool, unquoted_keys? : Bool, single_quotes? : Bool, extra_numbers? : Bool, extra_escapes? : Bool, extra_space? : Bool, depth? : Int, surrogates? : Bool, duplicates? : Duplicates) -> Flavor