#json(...) reader macro and tur/jsonStatus: the
#json(...)reader macro and the runtimetur/jsonlibrary are both shipping.
Turmeric handles JSON two ways:
#json(...) reader macro embeds a verbatim JSON blob
in source, validates it while compiling, and lowers it to a tur/json
tagged-node tree (the same value json/decode produces). No runtime parse
step.tur/json library (json/decode, json/encode) parses
and serializes JSON strings whose contents are not known until the program
runs.Because both produce the same node tree, #json(...) is effectively a
json/decode whose input is validated by the compiler.
#json(...) reader macroThe macro uses round parens as the outer fence, so a top-level object reads without a doubled brace and a top-level array has no doubled bracket:
#json({"a": 1, "b": 2}) ; object
#json([1, 2, 3]) ; array
#json(42) ; scalar
The JSON is parsed at compile time and emitted as tur/json constructor calls,
which the normal typechecker elaborates:
| JSON | Emitted constructor call |
|---|---|
{"a": 1} |
(json/object-put (json/object-new) "a" (json/int 1)) |
[1, 2, 3] |
(json/array-push (json/array-push (json/array-new) (json/int 1)) ...) |
"hello" |
(json/string "hello") |
42 |
(json/int 42) |
3.14 |
(json/float 3.14) |
true / false |
(json/bool 1) / (json/bool 0) |
null |
(json/null) -- a real null node |
Every node is a uniform handle (:int), so values can be heterogeneous and
nested -- the type of each value is carried as a runtime tag, recoverable with
json/type and the json/get-* accessors. This is the key difference from the
#map{...} / [...] data literals, which are monomorphic :int collections.
Retrieve object fields by key with json/get! (returns the value node) and
extract the scalar with the typed accessor:
(json/get-string (json/get! #json({"name": "alice", "age": 30}) "name")) ; => "alice"
(json/get-int (json/get! #json({"name": "alice", "age": 30}) "age")) ; => 30
(json/get is the total variant -- it returns an Option node; json/get!
panics on a missing key.)
Index into arrays with json/array-get / json/array-len:
(json/array-len #json([10, "x", true])) ; => 3
(json/get-int (json/array-get #json([10, "x", true]) 0)) ; => 10
(json/get-string (json/array-get #json([10, "x", true]) 1)) ; => "x"
null is genuinely distinct from 0/false -- it is a node whose
(json/type node) is 0:
(json/type #json(null)) ; => 0 (null)
(json/type #json(false)) ; => 1 (bool)
(json/type #json(0)) ; => 2 (int)
A Vec/HAMT stores int64 slots, and vec-of/hamt-of are monomorphic in
a single :int carrier -- so a collection's elements must all share one type
(the vec-of docstring spells this out). JSON, by contrast, is heterogeneous
([1, "x", true, null], mixed-type objects). Representing that on top of
homogeneous storage requires a boxed/tagged value: each value becomes the
same type -- a JSON node handle -- with the variation moved into a runtime tag.
That is exactly the tur/json node, which is why #json(...) targets it
rather than collapsing everything to :int (and null to a misleading 0).
Malformed JSON is reported at compile time with line/column pointing into the
#json(...) block:
| Code | Condition |
|---|---|
TUR-E0270 |
Malformed JSON inside a #json(...) block |
TUR-E0271 |
Unexpected EOF inside a #json(...) block |
The #json<Type>(...) form wraps its node tree in an ascription
(:: <node> Type). For a literal blob this is a compile-time type check on the
node's own representation; the typed-decode path lives in the tur/schema
reader family #json-str<T>(expr), which desugars to
(:: (decode! (json/decode expr)) T). See the
schema guide for HasSchema and typed decoding.
tur/jsonWhen the JSON is not known at compile time (read from a file, a socket, user input), use the runtime library:
(let [node (json/decode "{\"x\": 1, \"y\": 2}")]
...) ; node is a heap JSON value tree
(json/encode node) ; => "{\"x\":1,\"y\":2}"
json/decode returns a node tree (null / bool / int / float / string / array /
object); see stdlib/json.tur for the accessor API. Use the reader macro for
fixed, source-embedded data and tur/json for dynamic data.
| Need | Use |
|---|---|
| Embed a fixed JSON blob, validated at compile | #json(...) |
| Parse a JSON string only known at runtime | json/decode |
| Serialize a value to a JSON string | json/encode |
| Build a collection with computed slot values | #map{...} / #set{...} / [...] data literals |