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 可以是一个流、一个字符串或者是 nilnil表示从*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、或者nilt表示输出到*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-objectwith-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 ███████████████████████████████████████████████████████████▉

posted @ 2022-04-21 11:46  fmcdr  阅读(111)  评论(0编辑  收藏  举报