SkillAgentSearch skills...

Jzon

A correct and safe(er) JSON RFC 8259 reader/writer with sane defaults.

Install / Use

/learn @Zulu-Inuoe/Jzon

README

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.

Actions Status

Table of Contents

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, or jzon: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 positive integer
  • 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:parse will 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-comments controls if we allow single-line // comments and /**/ multiline block comments.
  • :allow-trailing-comma controls if we allow a single comma , after all elements of an array or object.
  • :allow-multiple-content controls if we allow for more than one element at the 'toplevel' see below
  • :key-fn is a function of one value which is called on object keys as they are read, or a boolean (see below)
  • :max-depth controls the maximum depth allowed when nesting arrays or objects.
  • :max-string-length controls 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 barring array-dimension-limit
  • t - 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 ' or vector (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 a pathname
  • 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

View on GitHub
GitHub Stars189
CategoryDevelopment
Updated9d ago
Forks17

Languages

Common Lisp

Security Score

100/100

Audited on Mar 18, 2026

No findings