argo is a JSON codec for Common Lisp whose value model keeps false,
null, and the empty array apart without making any of them a trap when
read. It decodes strictly by RFC 8259, refuses values that have no JSON form
before writing anything, and can bound documents from untrusted peers.
Encoding and value construction are done by yason; argo owns the value
model, the grammar, and the failure reporting around it.
| JSON | Lisp |
|---|---|
| object | an EQUAL hash table with string keys |
| array | a vector; a proper non-empty list also encodes as one |
| string | a string |
| number | an integer or a finite float; a ratio encodes as a float |
| true | T |
| false | the marker returned by (json-false) |
| null | NIL; :NULL also encodes as null |
Decoded false is a distinct marker so that a document re-encodes exactly,
while JSON-GET and JSON-GET-PRESENT report it as NIL. Both JSON false
and JSON null therefore read as Lisp false, and neither can be mistaken for
true, which is the trap in conventions that decode false as a symbol.
(let ((object (argo:json-decode "{\"enabled\":false,\"items\":[]}")))
(argo:json-get object "enabled") ; => NIL
(argo:json-false-p (gethash "enabled" object)) ; => T
(argo:json-encode object)) ; => "{\"enabled\":false,\"items\":[]}"Write false with JSON-FALSE or JSON-BOOLEAN. A NIL value encodes as
null, and so does an empty list, so build arrays that may be empty with
JSON-ARRAY. JSON-OBJECT builds an object from alternating string keys
and values, and JSON-OBJECT-COPY detaches a shallow copy.
The shallow copy and access functions accept any hash table without validating
its keys; JSON-OBJECT-P checks string keys. JSON-GET-PRESENT accepts
:false-value to present a caller-selected marker as NIL.
JSON-DECODE accepts exactly one JSON value surrounded by optional
whitespace. Trailing commas, unquoted or non-string keys, leading zeros, and
any text after the value are rejected. An iterative scan checks the grammar
before any value is built, so JSON-SYNTAX-ERROR reports the character
position and the expected token. Its report never quotes the source, which
may hold credentials. JSON-OBJECT-SOURCE-P tells whether a string is
exactly one JSON object.
Use :false-value to select a decoded false marker and :null-value to
select NIL or :NULL. Encoding accepts the same :false-value option,
translating markers during its existing validation/copy pass. Parser settings
bound by callers do not affect decoding. JSON-TRAILING-DATA, a subclass of
JSON-SYNTAX-ERROR, distinguishes trailing text from a malformed value.
JSON-ENCODE returns a compact string and JSON-ENCODE-UTF8 returns UTF-8
octets. The value is validated and copied first, so the caller’s data never
changes and nothing is written for a value with no JSON form: a symbol other
than the boolean and null markers, a character, a function, a complex
number, an infinite or NaN float, a dotted or circular list, a non-string
object key, or a container that contains itself all signal
JSON-UNREPRESENTABLE. Shared substructure is fine.
(argo:json-decode body :limits argo:*json-strict-limits*)
(argo:json-encode reply :limits (argo:make-json-limits :maximum-depth 32
:maximum-characters 65536))JSON-LIMITS bounds the source or output characters, nesting depth, total
nodes, characters in one string and across all strings, characters in one
object key, members in one object, elements in one array, and characters in
one number. A NIL bound is not enforced. When decoding, the bounds are
checked during the grammar scan, before any value is built and without
recursion, so a hostile document cannot exhaust the stack. When encoding,
JSON-ENCODE and JSON-ENCODE-UTF8 check the caller’s value the same way
before copying it, so a deeply nested or cyclic value is refused with a
condition rather than a stack overflow.
JSON-LIMIT-EXCEEDED names the exceeded constraint and its limit. Without
limits, nesting depth is bounded only by the Lisp stack.
Every condition is a JSON-ERROR with a JSON-ERROR-MESSAGE.
JSON-SYNTAX-ERROR- the source is not exactly one JSON value;
JSON-SYNTAX-ERROR-POSITIONis the failing character index. JSON-LIMIT-EXCEEDEDJSON-LIMIT-EXCEEDED-CONSTRAINTandJSON-LIMIT-EXCEEDED-LIMITdescribe the bound.JSON-UNREPRESENTABLEJSON-UNREPRESENTABLE-VALUEis the offending value.
./script/bootstrap
./script/check
./script/check --fix./script/check runs the tests, then checks the source with
cheque as cheque.sexp
configures it. --fix skips the tests and repairs what cheque can repair.
cheque and its tools are development dependencies: checks/qlfile lists them,
and the check installs them on first use into ~/.cache/cheque/tools/.
argo is distributed under the COLL-Attribution license in LICENSE.lisp.