vendor:json depends on vendor:edn for nothing, and borrowing a shape walk across that line would be a dependency for the sake of a resemblance. What carries over is the shape of the answer — one walk giving a type, the declarations that type needs and the expression that reads one; a refusal carried in a field rather than raised; a typed one-line constructor per collection; and a compile-error wrapped in a defn nothing calls. What is genuinely different is four things. Strings go through string-of and never through .text: .text is the raw interior with escapes undecoded, so a field read off it would hold a backslash and an n where the file meant a newline — which is the first two lines of the acceptance output and the reason they are two. Commas and colons are tokens rather than whitespace. An object's keys are strings, so a key has to be refused when it is not a name a program could write, and refused again when it carries an escape: the generated reader compares against the bytes as written, which costs no allocation per key and is only the same question when the name is written plainly. And there are no sets, so there is no map-key path and no fixed array — every collection is a (Vec T) and defjson is the smaller of the two by half. JSON has no integer type; the tokenizer draws the line at whether a number has a fraction or an exponent, which is the only line there is, so 1 derives i64 and 1.0 derives f64. That is the file's own distinction and the honest one to take. @x86 matches on it and @sanitize is clean.
54 lines
2.2 KiB
Plaintext
54 lines
2.2 KiB
Plaintext
;;;; defjson over a config file, and the escape that says it is not defedn.
|
|
;;;;
|
|
;;;; The same idea as edn-provide.flan and a different package: json depends on
|
|
;;;; edn for nothing and this program imports only json.
|
|
;;;;
|
|
;;;; "name" is the line that matters. Its value in the file is written
|
|
;;;; "tileset\nrunner", and JSON's `.text` is the RAW interior — escapes
|
|
;;;; undecoded — so a reader built on `.text` prints one line with a backslash
|
|
;;;; and an n in it. This prints two lines, which is `string-of` having been
|
|
;;;; used where a field is filled.
|
|
|
|
(import json "vendor:json")
|
|
|
|
(json/defjson Config "assets/json/config.json")
|
|
|
|
(defconst config (embed "assets/json/config.json"))
|
|
|
|
(defn main [] i32
|
|
(let [a (heap-allocator)
|
|
cfg (Config-of-bytes config a)]
|
|
;; Two lines, not one with a backslash in it.
|
|
(println (.name cfg))
|
|
(println (.port cfg))
|
|
(println (.scale cfg))
|
|
(println (if (.debug cfg) "yes" "no"))
|
|
(println (len (.layers cfg)))
|
|
;; 3 + 1 + 4 + 1 + 5. A length alone would pass on a Vec that was allocated
|
|
;; and never filled, so the sum is the claim.
|
|
(let [total (i64 0)]
|
|
(dotimes [i (len (.layers cfg))]
|
|
(set total (+ total (at (.layers cfg) i))))
|
|
(println total))
|
|
;; The nested structs, by the names the paths give them: Config-window and
|
|
;; Config-window-origin. Ordinary field loads, two deep.
|
|
(println (.w (.window cfg)))
|
|
(println (.h (.window cfg)))
|
|
(println (.x (.origin (.window cfg))))
|
|
(println (.y (.origin (.window cfg))))
|
|
(println "")
|
|
;; Drift, the same contract defedn's reader carries: the struct says "port"
|
|
;; and these bytes do not, and they carry a "host" it has never heard of.
|
|
(handler-bind
|
|
[(json/SchemaDrift [d]
|
|
(do (print (if (.extra? d) "extra " "missing "))
|
|
(print (.field d))
|
|
(print " in ")
|
|
(println (.struct d))))]
|
|
(let [c2 (Config-of-bytes
|
|
(bytes "{\"name\":\"x\",\"host\":\"h\",\"scale\":2.0,\"debug\":false,\"layers\":[1],\"window\":{\"w\":1,\"h\":1,\"origin\":{\"x\":0,\"y\":0}}}")
|
|
a)]
|
|
(println (.name c2))
|
|
(println (.port c2)))))
|
|
0)
|