// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// The four dialects and the pointer, in one run.
///
/// moon run src/examples/tour
#coverage.skip
fn main raise {
// One document, three ways of writing it. They read to the same tree.
let plain = @json.loads(
"{\"host\":\"localhost\",\"port\":8080,\"tls\":false}",
)
let source =
#|{
#| "host": "localhost", // where to bind
#| "port": 8080,
#| "tls": false, // a certificate is not set up yet
#|}
let commented = @jsonc.loads(source[:])
let loose = @json5.loads("{host:'localhost',port:8080,tls:false,}")
println("jsonc == json -> \{commented == plain}")
println("json5 == json -> \{loose == plain}")
// Writing is always strict JSON, whatever the document was written in.
println("compact -> \{@json.dumps(plain)}")
println("indented -> \{@json.dumps(plain, indent=2)}")
// A number keeps the digits it came with, so a document round-trips.
let wide = "[1e400,12345678901234567890,0.1]"
println("round trip -> \{@json.dumps(@json.loads(wide)) == wide}")
// JSON5 carries values JSON has no syntax for; they go out as null.
println(
"json5 extras -> \{@json5.dumps(@json5.loads("[Infinity,NaN,0xFF]"))}",
)
// A stream of records, one to a line.
let records = @lines.loads_all("{\"n\":1}\n{\"n\":2}\n{\"n\":3}\n")
println("records -> \{records.length()}")
// One place in a document, named.
let tree = @json.loads("{\"a\":{\"b\":[10,20,30]}}")
println("pointer -> \{@json.dumps(@pointer.get(tree, "/a/b/1"))}")
println("missing -> \{@pointer.has(tree, "/a/b/9")}")
// A patch is a list of operations, applied in order or not at all.
let ops = @patch.loads(
"[{\"op\":\"replace\",\"path\":\"/port\",\"value\":9090},{\"op\":\"add\",\"path\":\"/tls\",\"value\":true}]",
)
println("patch -> \{@json.dumps(@patch.apply(plain, ops))}")
// A merge patch looks like the document: a member that is not there is
// added, one that is there is replaced, and a null takes one away.
let patch_text = "{\"port\":9090,\"tls\":null}"
println(
"merge -> \{@json.dumps(@merge.merge(plain, @json.loads(patch_text)))}",
)
// A query rather than a pointer: every book under ten, wherever it is.
let shelf = @json.loads(
"{\"store\":{\"book\":[{\"title\":\"A\",\"price\":8.95},{\"title\":\"B\",\"price\":12.99},{\"title\":\"C\",\"price\":8.99}]}}",
)
let cheap = @path.select(shelf, "$..book[?@.price<10].title")
println("jsonpath -> \{@json.dumps(Json::array(cheap))}")
// The same tree as bytes, and back. Nothing about the document changed with
// the notation.
let body = @cbor.dump(plain)
println("cbor -> \{@hex.encode(body[:])}")
println("cbor == json -> \{@cbor.load(body[:]) == plain}")
// One document, one sequence of bytes — what a signature is taken over.
println("canonical -> \{@jcs.dumps(plain)}")
// A lenient dialect reads what JSON would refuse; a compact binary one
// carries the same tree in fewer bytes. Both come back to the same tree.
let loose2 = @hjson.loads("{ host: localhost, port: 8080, tls: false }")
println("hjson == json -> \{loose2 == plain}")
let packed = @cbd.dump(plain)
println("cbd == json -> \{@cbd.load(packed[:]) == plain}")
// A stream of records, written one at a time.
let sink = @sink.Sink::new()
sink.write(plain)
sink.write(plain)
println("sink records -> \{sink.length()}")
// The same stream read back, one at a time.
let src = @source.Source::new(sink.to_bytes()[:])
match src.read() {
Some(one) => println("source first -> \{@json.dumps(one)}")
None => println("source first -> none")
}
// A formatter rather than a dialect: four spaces a level, names sorted.
println(
"pretty -> \{@pretty.format(plain, style=@pretty.Format::new(indent=4, sort=true))}",
)
// A failure says where it happened.
try @json.loads("{\n \"a\": 1,\n \"b\": ?\n}") |> ignore catch {
e => {
let spot = e.at().unwrap()
println("failure -> line \{spot.line}, column \{spot.column}")
}
} noraise {
_ => println("failure -> none, which cannot happen")
}
}