Jzon
A correct and safe(er) JSON RFC 8259 reader/writer with sane defaults.
Install / Use
/learn @Zulu-Inuoe/JzonREADME
jzon
A correct and safe(er) JSON [RFC 8259][JSONRFC] reader/writer with sane defaults.
Please see the section Motivation and Features for a set of motivations driving jzon and why you should consider it over the other hundred options available for JSON in CL.
Please see the changelog for a list of changes between versions.
Table of Contents
- Quickstart
- Usage
- Motivation and Features
- Dependencies
- License
- Alternatives
Quickstart
jzon is on both Quicklisp and Ultralisp, and can be loaded via
(ql:quickload '#:com.inuoe.jzon)
Most users will simply use jzon:parse for reading, and jzon:stringify for writing. These mirror the JSON methods in JavaScript.
Note: Examples in this README can be copy-pasted in your REPL if you've got a nickname set up for jzon. To follow along with the examples, add a local nickname depending to your common lisp implementation:
SBCL
(sb-ext:add-package-local-nickname '#:jzon '#:com.inuoe.jzon)
Other
If you have an up to date version of uiop installed:
(uiop:add-package-local-nickname '#:jzon '#:com.inuoe.jzon)
Reading
jzon:parse will parse JSON and produce a CL value
(defparameter *ht* (jzon:parse "{
\"license\": null,
\"active\": false,
\"important\": true,
\"id\": 1,
\"xp\": 3.2,
\"name\": \"Rock\",
\"tags\": [
\"alone\"
]
}"))
(equalp 'null (gethash "licence" *ht*))
(equalp nil (gethash "active" *ht*))
(equalp t (gethash "important" *ht*))
(equalp 1 (gethash "id" *ht*))
(equalp 3.2d0 (gethash "xp" *ht*))
(equalp "Rock" (gethash "name" *ht*))
(equalp #("alone") (gethash "tags" *ht*))
Writing
jzon:stringify will serialize a value to JSON:
(jzon:stringify #(null nil t 42 3.14 "Hello, world!") :stream t :pretty t)
[
null,
false,
true,
42,
3.14,
"Hello, world!"
]
Type Mappings
jzon cannonically maps types per the following chart:
| JSON | CL |
|--------|-------------------------|
| true | symbol t |
| false | symbol nil |
| null | symbol null |
| number | integer or double-float |
| string | simple-string |
| array | simple-vector |
| object | hash-table (equal) |
Note the usage of symbol cl:null as a sentinel for JSON null
When writing, additional values are supported. Please see the section jzon:stringify.
Usage
As noted, jzon:parse and jzon:stringify suit most use-cases, this section goes into more detail, as well as an introduction to the jzon:writer interface.
jzon:parse
Function jzon:parse in &key max-depth allow-comments allow-trailing-comma allow-multiple-content max-string-length key-fn
=> value
- in - a
string,vector (unsigned-byte 8),stream,pathname, orjzon:span - max-depth - a positive
integer, or a boolean - allow-comments - a
boolean - allow-trailing-comma - a
boolean - allow-multiple-content - a
boolean - max-string-length -
nil,t, or a positiveinteger - key-fn - a designator for a function of one argument, or a boolean
value - a jzon:json-element (see Type Mappings)
Description
Reads JSON from in and returns a jzon:json-element per Type Mappings.
in can be any of the following types:
- string
- (vector (unsigned-byte 8)) - octets in utf-8
- stream - character or binary in utf-8
- pathname -
jzon:parsewill open the file for reading in utf-8 jzon:span- denoting a part of a string/vector
Tip: You can also use a displaced array to denote a region of an array without copying it.
The keyword arguments control optional features when reading:
:allow-commentscontrols if we allow single-line // comments and /**/ multiline block comments.:allow-trailing-commacontrols if we allow a single comma,after all elements of an array or object.:allow-multiple-contentcontrols if we allow for more than one element at the 'toplevel' see below:key-fnis a function of one value which is called on object keys as they are read, or a boolean (see below):max-depthcontrols the maximum depth allowed when nesting arrays or objects.:max-string-lengthcontrols the maximum length allowed when reading a string key or value.
max-string-length may be an integer denoting the limit, or
nil- No limit barringarray-dimension-limitt- Default limit
When either max-depth or max-string-length is exceeded, jzon:parse shall signal a jzon:json-parse-limit-error error.
allow-multiple-content
JSON requires there be only one toplevel element. Using allow-multiple-content tells jzon:parse to stop after reading one full toplevel element:
(jzon:parse "1 2 3" :allow-multiple-content t) #| => 1 |#
When reading a stream we can call jzon:parse several times:
(with-input-from-string (s "1 2 3")
(jzon:parse s :allow-multiple-content t) #| => 1 |#
(jzon:parse s :allow-multiple-content t) #| => 2 |#
(jzon:parse s :allow-multiple-content t)) #| => 3 |#
:warning: When reading numbers, null, false, or true, they must be followed by whitespace. jzon:parse shall signal an error otherwise:
(jzon:parse "123[1, 2, 3]" :allow-multiple-content t) #| error |#
This is to prevent errors caused by the lookahead necessary for parsing non-delimited tokens.
This is not required when using jzon:parse-next.
key-fn
When parsing objects, key-fn is called on each of that object's keys (simple-string):
(jzon:parse "{ \"x\": 0, \"y\": 1 }" :key-fn #'print)
"x"
"y"
#| #<HASH-TABLE :TEST EQUAL :COUNT 2 {1006942E83}> |#
the role of key-fn is to allow the user to control how keys end up as hash table keys. The default key-fn will share object keys between all objects during parse. See Object Key Pooling.
As an example, alexandria:make-keyword can be used to make object keys into keywords:
(jzon:parse "[ { \"x\": 1, \"y\": 2 }, { \"x\": 3, \"y\": 4 } ]" :key-fn #'alexandria:make-keyword)
(defparameter *v* *)
(gethash :|x| (aref *v* 0)) #| => 1 |#
(gethash :|y| (aref *v* 0)) #| => 2 |#
Pass nil to key-fn in order to avoid key pooling:
(jzon:parse "[ { \"x\": 1, \"y\": 2 }, { \"x\": 3, \"y\": 4 } ]" :key-fn nil)
(defparameter *v* *)
(gethash "x" (aref *v* 0)) #| => 1 |#
(gethash "y" (aref *v* 0)) #| => 2 |#
This may help speed up parsing on highly heterogeneous JSON.
Note: It is recommended leave this as default. The performance improvement is usually not substantive enough to warrant duplicated strings, and interning strings from untrusted JSON is a security risk.
jzon:span
Function jzon:span in &key start end
=> span
- in - a
string,stream' orvector (unsigned-byte 8) - start, end - bounding index designators of sequence. The defaults for start and end are 0 and nil, respectively.
span a span object representing the range.
Description
Create a span to be used in jzon:parse, or jzon:make-parser in order to specify a bounded start and end for a string, stream or vector.
NOTE: For streams, only input streams are allowed.
Example
(jzon:parse (jzon:span "garbage42moregarbage" :start 7 :end 9))
#| => 42 |#
jzon:stringify
Function jzon:stringify value &key stream pretty coerce-key replacer
=> result
- value - a
jzon:json-element, or other value (see below) - stream - a destination like in
format, or apathname - pretty - a boolean
- replacer - a function of two arguments (see below)
- coerce-key - a function of one argument, or nil (see below)
- max-depth - a positive integer, or a boolean
result - nil, or a string
Description
Serializes value to JSON and writes it to stream.
If pretty is true, the output is formatted with spaces and newlines.
max-depth limits the depth of nesting arrays/objects. Use nil to disable it, or t to set to default.
In addition to serializing jzon:json-element values per Type Mappings, jzon:stringify allows other values.
See [Additio
Related Skills
node-connect
338.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.6kCommit, push, and open a PR
