add cl-json-pointer with shorter functions

This commit is contained in:
vindarel 2023-03-10 13:44:53 +01:00
parent 890c4a8a55
commit b33d99e4d2
6 changed files with 176 additions and 6 deletions

View file

@ -39,7 +39,8 @@
:closer-mop
:cl-ansi-text
:cl-csv
:shasht
:shasht ;; json
:cl-json-pointer
:dissect
:fset
:generic-cl
@ -131,6 +132,7 @@
:components ((:module "src"
:components
((:file "packages")
(:file "json-pointer-minus")
(:file "ciel")))
(:module "src/more-docstrings"
:components

View file

@ -6,7 +6,7 @@ It's Common Lisp, batteries included.
It comes in 3 forms:
- a binary, to run CIEL **scripts**.
- a binary, to run CIEL **scripts**: fast start-up times, standalone binary, built-in utilities.
- a simple full-featured **REPL** for the terminal.
- a **Lisp library**.

View file

@ -48,7 +48,7 @@
var footer = [
'<hr/>',
'<footer style="text-align: right;">',
'<span style=\"color: dimgrey\">CIEL developers 2022. <a href="https://github.com/sponsors/vindarel/">Show your love!</a></span>',
'<span style=\"color: dimgrey\">CIEL developers 2023. <a href="https://github.com/sponsors/vindarel/">Show your love!</a></span>',
'</footer>'
].join('');

View file

@ -14,7 +14,7 @@ It's always
(access my-structure :elt)
```
for an alist, a hash-table, a struct, an object… Use `accesses` for nested access (specially useful with JSON).
for an alist, a hash-table, a struct, an object… Use `accesses` for nested access (specially useful with JSON). See also `json-pointer`.
### Hash-table utilities (Alexandria and Serapeum)
@ -161,9 +161,13 @@ See also:
### JSON
We use [shasht](https://github.com/yitzchak/shasht). It has a `json` nickname.
We use [shasht](https://github.com/yitzchak/shasht) to read and write JSON. It has a `json` nickname.
It is one of the newest and one of the best JSON handling libraries.
We use [cl-json-pointer](https://github.com/y2q-actionman/cl-json-pointer/) to refer to nested keys with a short syntax. It has a `json-pointer` nickname.
#### read-json, write-json
Shasht 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:
@ -342,6 +346,70 @@ 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.
#### JSON pointer
JSON pointers ([RFC 6901](https://www.rfc-editor.org/rfc/rfc6901)) are especially handy to manipulate nested objects. We can access, set and delete keys and values with short pointers, which are strings.
Example:
~~~lisp
(defparameter *json-data*
"{
\"foo\": [\"bar\", \"baz\"]
}")
(let ((obj (json:read-json *json-data*)))
(json-pointer:get-by obj "/foo/0"))
;; => "bar"
~~~
[cl-json-pointer](https://github.com/y2q-actionman/cl-json-pointer/) has some lengthy function names:
- `get-by-json-pointer`, `set-by-json-pointer`… especially if we access them with the `json-pointer:` prefix.
We provide shorten ones:
- `get-by` `(obj pointer)`
- `set-by` `(obj pointer value)`
- `update-by` `(place pointer value)`
- `add-by` `(obj pointer value)`
- `delete-by` `(obj pointer)`
- `deletef-by` `(place pointer)`
- `remove-by` `(obj pointer)`
- `exists-p-by` `(obj pointer)`
`get-by` traverses OBJ with POINTER and returns three values:
- the found value (nil if not found),
- a generalized boolean saying the existence of the place pointed by POINTER,
- and NIL.
JSON-POINTER functions actually take a dict (hash-table) as first argument.
Examples:
~~~lisp
(json-pointer:get-by (dict \"a\"
(dict \"aa\" 11))
\"/a/aa\")
;; => 11
~~~
Parse a JSON string with `shasht:read-json` before feeding the result to json-pointer:
~~~lisp
(defvar *json-string* \"{\\\"foo\\\": [\\\"1\\\", \\\"2\\\"]}\")
(let ((obj (shasht:read-json *json-string*)))
(json-pointer:get-by obj \"/foo\"))
;; =>
#(\"1\" \"2\")
T
NIL
~~~
A JSON pointer starts with a "/".
## Date and time

View file

@ -0,0 +1,99 @@
;;
;; JSON-POINTER
;;
;; cl-json-pointer has lengthy functions: get-by-json-pointer, add-by-json-pointer, etc.
;; Let's create shorter ones: get-by etc.
;;
;; But why doesn't it accept a JSON string as input?!
(in-package :ciel)
(setf cl-json-pointer:*json-object-flavor* :shasht)
(defun json-pointer-get-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*))
"Traverse OBJ with POINTER and return three values:
- the found value (`nil' if not found),
- a generalized boolean saying the existence of the place pointed by POINTER,
- and NIL.
GET-BY is a shorter name of cl-json-pointer:get-by-json-pointer added by CIEL.
In CIEL, we use the SHASHT library to handle JSON.
SHASHT returns a hash-table.
JSON-POINTER functions take a dict (hash-table) as first argument.
Examples:
(json-pointer:get-by (dict \"a\"
(dict \"aa\" 11))
\"/a/aa\")
;; => 11
Parse a JSON string with SHASHT:READ-JSON before feeding the result to json-pointer:
(defvar *json-string* \"{\\\"foo\\\": [\\\"1\\\", \\\"2\\\"]}\")
(let ((obj (shasht:read-json *json-string*)))
(json-pointer:get-by obj \"/foo\"))
;; =>
#(\"1\" \"2\")
T
NIL
"
(cl-json-pointer:get-by-json-pointer obj pointer :flavor flavor))
#+(or)
(defvar *json-string* "{\"foo\": [\"1\", \"2\"]}")
(defun json-pointer-set-by (obj pointer value &key (flavor cl-json-pointer:*json-object-flavor*))
"Traverse OBJ with POINTER, set VALUE into the pointed place, and return the modified OBJ."
(cl-json-pointer:set-by-json-pointer obj pointer value :flavor flavor))
(defun json-pointer-update-by (place pointer value &key (flavor cl-json-pointer:*json-object-flavor*))
"Set the result of SET-BY to the referred PLACE.
UPDATE-BY is a modify macro for SET-BY (like PUSH or INCF)."
(cl-json-pointer:update-by-json-pointer place pointer value :flavor flavor))
(defun json-pointer-add-by (obj pointer value &key (flavor cl-json-pointer:*json-object-flavor*))
"Works as `set-by', except this tries to make a new list when setting to lists."
(cl-json-pointer:add-by-json-pointer obj pointer value :flavor flavor))
(defun json-pointer-delete-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*))
"Traverse OBJ with POINTER, delete the pointed place, and return the modified OBJ."
(cl-json-pointer:delete-by-json-pointer obj pointer :flavor flavor))
(defun json-pointer-deletef-by (place pointer &key (flavor cl-json-pointer:*json-object-flavor*))
"Set the result of DELETE-BY to the referred PLACE.
This is a modify macro for DELETE-BY (like PUSH or INCF)."
(cl-json-pointer:deletef-by-json-pointer place pointer :flavor flavor))
(defun json-pointer-remove-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*))
"Like `delete-by', except this tries to make a new list when deleting from lists."
(cl-json-pointer:remove-by-json-pointer obj pointer :flavor flavor))
(defun json-pointer-exists-p-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*))
"Traverse OBJ with POINTER and return the existence of the place pointed by POINTER."
(cl-json-pointer:exists-p-by-json-pointer obj pointer :flavor flavor))
(defun json-pointer-shorten-functions ()
"Shorten function names inside the cl-json-pointer package."
(let ((tuples (list
(list "GET-BY" #'json-pointer-get-by)
;; NAME NEW FUNCTION
(list "ADD-BY" #'json-pointer-add-by)
(list "SET-BY" #'json-pointer-set-by)
(list "UPDATE-BY" #'json-pointer-update-by)
(list "DELETE-BY" #'json-pointer-delete-by)
(list "DELETEF-BY" #'json-pointer-deletef-by)
(list "REMOVE-BY" #'json-pointer-remove-by)
(list "EXISTS-P-BY" #'json-pointer-exists-p-by))))
(loop for tuple in tuples
for sym = (intern (first tuple) 'cl-json-pointer)
do (export sym 'cl-json-pointer)
(setf (symbol-function sym) (second tuple))
collect sym)))
(json-pointer-shorten-functions)

View file

@ -20,6 +20,7 @@
(:csv :cl-csv)
(:http :dexador)
(:json :shasht)
(:json-pointer :cl-json-pointer)
(:routes :easy-routes)))