diff --git a/ciel.asd b/ciel.asd
index 10acf36..5c8d00d 100644
--- a/ciel.asd
+++ b/ciel.asd
@@ -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
diff --git a/docs/README.md b/docs/README.md
index 1afe582..5ba5a8c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -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**.
diff --git a/docs/index.html b/docs/index.html
index 48cf707..5363c78 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -48,7 +48,7 @@
var footer = [
'
',
''
].join('');
diff --git a/docs/libraries.md b/docs/libraries.md
index 635e475..ae27385 100644
--- a/docs/libraries.md
+++ b/docs/libraries.md
@@ -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
diff --git a/src/json-pointer-minus.lisp b/src/json-pointer-minus.lisp
new file mode 100644
index 0000000..b7d4c7a
--- /dev/null
+++ b/src/json-pointer-minus.lisp
@@ -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)
diff --git a/src/packages.lisp b/src/packages.lisp
index 8cc08fc..e101a4d 100644
--- a/src/packages.lisp
+++ b/src/packages.lisp
@@ -20,6 +20,7 @@
(:csv :cl-csv)
(:http :dexador)
(:json :shasht)
+ (:json-pointer :cl-json-pointer)
(:routes :easy-routes)))