shasht - Common Lisp JSON library
shasht - Common Lisp JSON library
读取JSON
读取和解析json的主要接口是read-json
函数
(read-json &optional input-stream-or-string (eof-error-p t) eof-value single-value-p)
参数:
-
input-string-or-string
可以是一个流、一个字符串或者是nil
,nil
表示从*standard-input*
读取。 -
eof-error-p
-
eof-value
这两个参数的作用与read
函数的参数一样 -
single-value-p
如果这个参数的值为t
,那么read-json
的输入将被假定为单个值,末尾的额外标记将导致错误
有一些全局变量会影响JSON数据的解析:
-
common-lisp:*read-default-float-format*
控制在读取浮点数时使用的格式 -
*read-default-true-value*
控制true
的返回值,默认为t
-
*read-default-false-value*
控制false
的返回值,默认为nil
-
*read-default-null-value*
控制null
的返回值,默认为:null
-
*read-default-array-format*
控制json数组的返回类型,可选值为:vector
或:list
,默认为:vector
-
*read-default-object-format*
控制json对象的返回类型,当前支持:hash-table
,alist
,plist
。默认为:hash-table
。 -
*read-length*
数组或对象中值的最大数量。默认为nil
,禁用长度检查。 -
*read-level*
读取数组和对象时允许的最大级别(深度),默认为nil
,禁用级别检查
read-json
有一个关键字变量的变种read-json*
,它根据关键字参数来设置各种动态变量:
(read-json* :stream nil
:eof-error t
:eof-value nil
:single-value nil
:true-value t
:false-value nil
:null-value :null
:array-format :vector
:object-format :hash-table
:float-format 'single-float
:length nil
:level nil)
生成JSON
序列化和写入JSON的主要接口是write-json
函数
(write-json value &optional (output-stream t))
output-stream
参数可以是一个流、t
、或者nil
。t
表示输出到*standard-output*
,nil
表示输出为字符串。如果输出为字符串,则write-json
的返回值为该字符串,否则就返回初始的value
。
同样有一些动态变量能够影响 write-json
的行为:
-
common-lisp:*print-pretty*
是否使用 pretty-print 格式化输出 -
*write-indent-string*
缩进使用的字符,默认为#\space
-
*write-ascii-encoding*
如果为t
,则任何非ASCII值都将使用Unicode转义序列进行编码。默认为nil
-
*write-true-values*
true
的编码,默认为'(t :ture)
-
*write-false-values*
false
的编码,默认为'(nil :false)
-
*write-null-values*
null
的编码,默认为(:null)
-
*write-alist-as-object*
如果为真,则将 alist 编码为一个 JSON 对象,默认为nil
-
*write-plist-as-object*
如果为真,则将 plist 编码为一个 JSON 对象,默认为nil
-
*write-empty-array-values*
将会被作为空数组写入的值 -
*write-empty-object-values*
将会被作为空对象写入的值 -
*write-array-tags*
一个值的列表,其car
标记列表的cdr
应该被当作一个数组。默认值为'(:array)
-
*write-object-alist-tags*
用 alist 表示 JSON 对象的标记,默认为'(object-alist)
-
*write-object-plist-tags*
用 plist 表示 JSON 对象的标记,默认为'(object-plist)
JSON 数据实际的序列化工作是由泛型函数print-json-value
完成的,该函数可以针对特定的值类型进行专门化。
(print-json-value value output-stream)
write-json
同样有一个拥有关键字参数的变种write-json*
(write-json* value :stream t
:ascii-encoding nil
:true-values '(t :true)
:false-values '(nil :false)
:null-values '(:null)
:empty-array-values '(:empty-array)
:empty-object-values '(:empty-object)
:array-tags '(:array)
:object-alist-tags '(:object-alist)
:object-plist-tags '(:object-plist)
:alist-as-object nil
:plist-as-object nil
:pretty nil
:indent-string " ")
辅助函数
为了方便 shasht 的序列化功能,有许多可用的辅助函数,要打印 JSON 字符串,可以使用:
(write-json-string value output-stream)
为了简化对象和数组的序列化,可以使用with-json-object
和with-json-array
。这两个宏的第一个参数都是一个流。
在with-json-object
的主体中,应该使用(print-json-key-value key value output-stream)
函数来输出一个键值对。在with-json-array
的主体中,应该使用(print-json-value value output-stream)
函数来输出单个的值。示例用法可以在源代码中看到。
JSON 数组和对象的字面量表示
有时,通过序列化辅助函数使用增量序列化并不适合应用程序。例如,可能需要将不同的片段组装成JSON,然后在序列化之前进行分析。这在jupiter小部件协议中经常发生。该JSON中的二进制数据值是从JSON中提取出来,并以二进制形式传输,以减少网络负载。
纯粹为了JSON序列化目的而构造哈希表有些困难,而且可能会浪费内存,因此可以使用各种关键字字面量来构造JSON对象,以避免这种情况。还有用于JSON数组的字面量,尽管它们可能没有那么有用。这些字面量存在shasht中主要是为了一致性。下面展示了使用这些字面量的一些示例。
* (shasht:write-json
'(:object-alist ("a" . :empty-array)
("b" . :empty-object)
("c" . (:object-plist "d" 1
"e" (:array 1 2 3)))))
{
"a": [],
"b": {},
"c": {
"d": 1,
"e": [
1,
2,
3
]
}
}
(:OBJECT-ALIST ("a" . :EMPTY-ARRAY) ("b" . :EMPTY-OBJECT)
("c" :OBJECT-PLIST "d" 1 "e" (:ARRAY 1 2 3)))
表示这些字面量的标记可以通过上文所介绍的*write-empty-array-values*
, *write-object-alist-tags*
, *write-object-plist-tags*
等动态变量进行配置。
这些字面量只用于序列化,不能用于双向转换。因此,无法读取用这种表示法表示的JSON。
数据类型映射
Common Lisp 和 JSON 之间的类型映射不是一对一的,而且存在歧义,这是由于 Common Lisp 将nil
同时视为空列表以及布尔值“假”。JSON中还包括null
,在 Common Lisp 中没有明确地数据类型来表示 NULL 值。因此,shasht 在 Common Lisp 与 JSON 的类型映射中做了某些取舍,这可能不是其它应用程序的理想选择。shasht 最初被设计为一个很好的双向编码/解码器,用于 common-lisp-jupyter 的网络协议。
下表给出了默认映射的简要说明。有关单个类型的映射以及如何配置映射的详细信息,请参阅该表后面的部分。
Common Lisp | JSON | |
---|---|---|
integer | <-> | 没有小数或指数部分的数字 |
float | <-> | 有小数或指数部分的数字 |
ratio | -> | 有小数或指数部分的数字 |
rational | -> | 有小数或指数部分的数字 |
string | <-> | string |
character | -> | string |
pathname | -> | string |
symbol | -> | string |
vector | <-> | array |
multi-dimensional array | -> | nested array |
non nil list | -> | array |
hashtable | <-> | object |
standard object | -> | object |
structure object | -> | object |
t |
<-> | true |
:true |
-> | true |
nil |
<-> | false |
:false |
-> | false |
:null |
<-> | null |
:empty-array |
-> | [] |
:empty-object |
-> | {} |
'(:array 1 2 3) |
-> | [1,2,3] |
'(:object-alist ("a" . 1) ("b" . 2)) |
-> | |
'(:object-plist "a" 1 "b" 2) |
-> |
数字映射
当数字字面值中出现小数或指数时,从JSON读取的数字的格式受到cl:*read-default-float-format*
的影响。这与cl:read
的行为相同。为了读取具有较大指数的JSON数字,需要执行如下操作。
(shasht:read-json "[2.232e75]" :float-format 'double-float)
数组映射
动态变量 *read-default-array-format*
和*write-empty-array-values*
和*write-array-tags*
都会影响 JSON到 Common Lisp 向量和列表的转换。Common Lisp 向量和多维数组总是转换为 JSON 数组。默认情况下,JSON 数组被读取为 Common Lisp 向量。使用默认设置,只有不满足其他映射规则的非空列表才被转换为 JSON 数组。
如果想将列表作为 JSON 数组的默认转换目标,那么需要为*read-default-false-value*
, *read-default-array-format*
和*write-false-value*
设置适当的值,因为在默认映射中nil
映射为false
。例如,可以执行以下操作:
(let ((shasht:*read-default-false-value* :false)
(shasht:*read-default-array-format* :list)
(shasht:*write-false-values* '(:false)))
(shasht:read-json ...)
(shasht:write-json ...))
如果列表的car
与*write-array-tags*
, *write-object-alist-tags*
或*write-object-plist-tags*
的值相同(eql),则该列表仍然会被转换为适当的数组或对象。要完全禁用些行为,上述变量需要绑定为nil
,或者可以这样做。
(shasht:write-json '(1 2 3) :false-value '(:false) :array-tags nil
:object-alist-tags nil :object-plist-tags nil)
在这种情况下,数组的映射变成了:
Common Lisp | JSON | |
---|---|---|
vector | -> | array |
multi-dimensional array | -> | nested array |
list | <-> | array |
对象映射
动态变量*read-default-object-format*
、*write-alist-as-object*
、*write-plist-as-object*
、*write-empty-object-values*
、*write-object-alist-tags*
、*write-object-plist-tags*
都会影响JSON对象到Common Lisp哈希表、list和plist的映射。Common Lisp哈希表总是被编写为JSON对象。默认情况下,JSON对象被读取为Common Lisp哈希表。
要将 JSON 对象读取为 alist, 需要为动态变量 *read-default-object-format*
, *write-alist-as-object*
, *read-default-false-value*
, 以及 *write-false-values*
设置合适的值。例如,下面的代码将使用alist作为默认的JSON对象格式,并使用:false
作为 JSON 的 false
值。
(let ((shasht:*read-default-object-format* :alist)
(shasht:*write-alist-as-object* t)
(shasht:*read-default-false-value* :false)
(shasht:*write-false-values* '(:false)))
(shasht:read-json ...)
(shasht:write-json ...))
在这种情况下,对象的映射变成了:
Common Lisp | JSON | |
---|---|---|
hashtable | -> | object |
alist | <-> | object |
standard object | -> | object |
structure object | -> | object |
下面的操作针对 plist 实现相同的功能
(let ((shasht:*read-default-object-format* :plist)
(shasht:*write-plist-as-object* t)
(shasht:*read-default-false-value* :false)
(shasht:*write-false-values* '(:false)))
(shasht:read-json ...)
(shasht:write-json ...))
兼容性
尽管JSON规范很简洁,但在某些方面是模糊的,因此实现的准确兼容性通常是不符合标准的。没有全面的测试,就很难确定是否符合规范。JSONTestSuite 包含了超过300个读取测试,包括那些规范中模棱两可的测试。shasht 的测试套件中包括了所有这些读取测试还有各种 write 测试。有关 JSON 的 Common Lisp 实现的兼容性比较,请参阅兼容性比较
Benchmarks
可以使用 tests/bench.lisp 进行简单的基准测试。对于SBCL,以下结果是典型的。
JSON Read Times
0 7.002169e-6 1.4004338e-5
˫--------------------------------+--------------------------------˧
cl-json ███████████████████████████████████▉
jonathan ████████▊
json-streams ███████████████████████████████████████████████████████████████████
jsown █████████▋
shasht █████████████▏
st-json █████████████████████████████████▊
yason ████████████████████████████████████████▉
JSON Write Times
0 6.2018808e-6 1.24037615e-5
˫--------------------------------+--------------------------------˧
cl-json ███████████████████████████████████████████████████████████████████
jonathan ████████████████████████████████████▍
json-streams ███████████████████████████████▎
jsown ████████████████████████████████████████▉
shasht ███████████▍
st-json ███████▋
yason ████████████████████████████████████▏
JSON Read/Write Times
0 1.1348141e-5 2.2696282e-5
˫--------------------------------+--------------------------------˧
cl-json ███████████████████████████████████████████████████████████████████
jonathan █████████████████████████████████████▍
json-streams ██████████████████████████████████████████████████████████████▉
jsown ██████████████████████████████▎
shasht ██████████████████████▏
st-json ███████████████████████████▋
yason ███████████████████████████████████████████████████████████▉