JSON: use Shasht instead of cl-json

This commit is contained in:
vindarel 2022-09-21 15:57:27 +02:00
parent b7b17f450d
commit 4a8a5bce7b
4 changed files with 160 additions and 33 deletions

View file

@ -26,7 +26,7 @@
:closer-mop
:cl-ansi-text
:cl-csv
:cl-json
:shasht
:dissect
:fset
:generic-cl

View file

@ -7,7 +7,7 @@
- cl-ansi-text: ANSI control string characters, focused on color
- cl-cron: A simple tool that provides cron like facilities directly inside of common lisp. For this to work properly note that your lisp implementation should have support for threads
- cl-csv: Facilities for reading and writing CSV format files
- cl-json: JSON in Lisp. JSON (JavaScript Object Notation) is a lightweight data-interchange format.
- shasht: Common Lisp JSON reading and writing for the Kzinti. The primary interface to parsing and reading JSON is the `read-json` function. The primary interface to serializing and writing JSON is the `write-json` function.
- cl-ppcre: Perl-compatible regular expression library
- cl-punch: Scala-like anonymous lambda literal
- cl-reexport: Reexport external symbols in other packages.

View file

@ -161,35 +161,82 @@ See also:
### JSON
We use [cl-json](https://common-lisp.net/project/cl-json/cl-json.html) ([GitHub](https://github.com/hankhero/cl-json)). It has a `json` nickname.
We use [shasht](https://github.com/yitzchak/shasht). It has a `json` nickname.
To encode an object to a string, use `encode-json-to-string`:
It is one of the newest and one of the best JSON handling libraries.
To encode an object to a stream (standard output, a string, or another
stream), use `write-json`. Its signature is:
```lisp
(json:encode-json-to-string (list (dict :a 1)))
;; "[{\"A\":1}]"
(write-json value &optional (output-stream t))
```
To decode from a string: `decode-json-from-string`.
Example:
To encode or decode objects from a *stream*, use:
- `encode-json object &optional stream`
- `decode-json &optional stream`
as in:
By default, write to standard output:
```lisp
(with-output-to-string (s)
(json:encode-json (dict :foo (list 1 2 3)) s))
;; "{\"FOO\":[1,2,3]}"
(with-input-from-string (s "{\"foo\": [1, 2, 3], \"bar\": true, \"baz\": \"!\"}")
(json:decode-json s))
;; ((:|foo| 1 2 3) (:|bar| . T) (:|baz| . "!"))
(json:write-json (list (dict :a 1)))
;; => printed representation:
[
{
"A": 1
}
]
;; => and the returned object:
(
(dict
:A 1
) )
```
cl-json can encode and decode from objects. Given a simple class:
Note how Shasht returns a hash-table, that is handily constructed with
our `dict` representation.
To encode an object and print to a string, use the final `output-stream` argument to `nil`:
~~~lisp
(shasht:write-json (list (dict :a 1)) nil)
;; =>
"[
{
\"A\": 1
}
]"
~~~
To encode or decode objects from a stream or a string, use `read-json`:
```lisp
(let ((string "{\"foo\": [1, 2, 3], \"bar\": true, \"baz\": \"!\"}"))
(json:read-json string))
;; =>
(dict
"foo" #(1 2 3) ;; <= an array
"bar" T
"baz" "!"
)
```
Note how the `[1, 2, 3]` list was formatted to a vector (`#(1 2
3)`). Shasht gives us many options as dynamic variables that influence
the parsing (see its README and below), in that case we can change
`*read-default-array-format*` to `:list`:
~~~lisp
(let ((json:*read-default-array-format* :list))
(let ((string "{\"foo\": [1, 2, 3], \"bar\": true, \"baz\": \"!\"}"))
(json:read-json string)))
;; =>
(dict
"foo" '(1 2 3) ;; <= now a list
"bar" T
"baz" "!"
)
~~~
Shasht can **encode and decode from objects**. Given a simple class:
```lisp
(defclass person ()
@ -200,19 +247,103 @@ cl-json can encode and decode from objects. Given a simple class:
We can encode an instance of it:
```lisp
(json:encode-json-to-string (make-instance 'person :name "you"))
;; "{\"NAME\":\"you\",\"LISPER\":true}"
(json:write-json (make-instance 'person :name "you"))
;; =>
{
"NAME": "you",
"LISPER": true
}
#<PERSON {1007FDDDC3}>
```
By default, cl-json wants to convert our lisp symbols to camelCase, and the JSON ones to lisp-case. We disable that in the `ciel-user` package.
Shasht options
==============
You can set this behaviour back with:
*(See its README for possible updates)*
Parsing (reading) options:
- `common-lisp:*read-default-float-format*` — Controls the floating-point format
that is to be used when reading a floating-point number.
- `*read-default-true-value*` — The default value to return when reading a true
token. Initially set to `t`.
- `*read-default-false-value*` — The default value to return when reading a
false token. Initially set to `nil`.
- `*read-default-null-value*` — The default value to return when reading a null
token. Initially set to `:null`.
- `*read-default-array-format*` — The default format to use when reading an
array. Current supported formats are `:vector` or `:list`. Initially set to
`:vector`.
- `*read-default-object-format*` — The default format to use when reading an
object. Current supported formats are `:hash-table`, `:alist` or `:plist`.
Initially set to `:hash-table`.
- `*read-length*` — The maximum number of values in an array or an object.
Initially set to `nil` which disables length checking.
- `*read-level*` — The maximum number of levels to allow during reading for
arrays and objects. Initially set to `nil` which disables level checking.
There is also a keyword variant `read-json*` which will set the various dynamic
variables from supplied keywords.
```lisp
(setf json:*json-identifier-name-to-lisp* #'json:camel-case-to-lisp)
(setf json:*lisp-identifier-name-to-json* #'json:lisp-to-camel-case)
(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)
```
Serialization (writing) options:
- `common-lisp:*print-pretty*` — If true then a simple indentation algorithm
will be used.
- `*write-indent-string*` — The string to use when indenting objects and arrays.
Initially set to `#\space`.
- `*write-ascii-encoding*` — If true then any non ASCII values will be encoded
using Unicode escape sequences. Initially set to `nil`.
- `*write-true-values*` — Values that will be written as a true token. Initially
set to `'(t :true)`.
- `*write-false-values*` — Values that will be written as a false token.
Initially set to `'(nil :false)`.
- `*write-null-values*` — Values that will be written as a null token. Initially
set to `(:null)`.
- `*write-alist-as-object*` — If true then assocation lists will be written as
an object. Initially set to `nil`.
- `*write-plist-as-object*` — If true then property lists will be written as an
object. Initially set to `nil`.
- `*write-empty-array-values*` — A list of values that will be written as an
empty array.
- `*write-empty-object-values*` — A list of values that will be written as an
empty object.
- `*write-array-tags*` — A list of values whose appearance in the CAR of a list
indicates the CDR of the list should be written as an array. Initially set to
`'(:array)`.
- `*write-object-alist-tags*` — A list of values whose appearance in the CAR of
a list indicates the CDR of the list is an alist and should be written as an
object. Initially set to `'(:object-alist)`.
- `*write-object-plist-tags*` — A list of values whose appearance in the CAR of
a list indicates the CDR of the list is a plist and should be written as an
object. Initially set to `'(:object-plist)`.
The actual serialization of JSON data is done by the generic function
`print-json-value` which can be specialized for additional value types.
```lisp
(print-json-value value output-stream)
```
There is also a keyword variant `write-json*` which will set the various dynamic
variables from supplied keywords and will default to the current dynamic value
of each keyword.
Date and time
-------------

View file

@ -375,7 +375,8 @@ We currently only try this with serapeum. See *deps/serapeum/sequences-hashtable
(uiop:define-package ciel-user
(:use :cl :ciel)
(:local-nicknames (:csv :cl-csv)
(:http :dexador)))
(:http :dexador)
(:json :shasht)))
;TODO: a conflict between Serapeum and generic-cl
(uiop:define-package generic-ciel
@ -392,11 +393,6 @@ We currently only try this with serapeum. See *deps/serapeum/sequences-hashtable
;; by using cl-syntax (Jonathan, Djula).
;; (pythonic-string-reader:enable-pythonic-string-syntax)
;; cl-json wants to convert our lisp symbols to camelCase, and the JSON ones to lisp-case.
;; We disable that.
(setf json:*json-identifier-name-to-lisp* #'identity)
(setf json:*lisp-identifier-name-to-json* #'identity)
;; Limit the maximum default output.
(setf *print-lines* 1000)
(setf *print-level* 20)