This library provides functions to resolve JSON pointers (as defined in RFC6901) within parsed JSON objects, also supporting remote resolution (fetching JSON from a URI).
JSON pointers can be resolved whether your JSON is parsed as an alist, plist or a hash table.
It also includes functions to replace JSON references ($ref
) as used in JSON schema and OpenAPI.
First add the library to your load path.
(require 'jsonp)
;; Resolve a JSON pointer within a parsed JSON object
(let ((json-object (json-read-from-string "{\"a\": {\"b\": 1}}"))
(pointer "/a/b"))
(jsonp-resolve json-object pointer)) ;; Returns 1
;; Resolve a remote JSON pointer
;; jsonp-remote configures how to fetch and parse the remote JSON
(jsonp-resolve-remote (jsonp-remote) "https://example.com/data.json#/a/b")
;; Resolve a JSON pointer without signalling errors
(jsonp-resolve-safe json-object "/non/existent/path") ;; Returns nil
;; Replace JSON references
;; THIS MODIFIES THE ARGUMENT OBJECT!
(let ((json-object (json-read-from-string "{\"a\": {\"$ref\": \"#/b\"}, \"b\": 1}")))
(jsonp-replace-refs json-object)) ;; Returns '((a . 1) (b . 1))
- Emacs 28.1 or later
When resolving remote JSON pointers, you can customize the JSON parser and the URL fetcher using an instance of the jsonp-remote
class.
To use the default settings, just call jsonp-remote
with no arguments
;; Resolve a remote JSON pointer with default settings
(jsonp-resolve-remote (jsonp-remote) "https://example.com/data.json#/a/b")
Note that the default URL fetcher is url-retrieve-synchronously
, which blocks.
Responses are cached, so in most cases performance should be adequate.
The JSON parser function should accept a single string value and return an object.
;; custom JSON parser
(let* ((json-as-plist (lambda (string) (json-parse-string string :object-type 'plist)))
(my-remote (jsonp-remote :json-parser json-as-plist)))
(jsonp-resolve-remote my-remote "https://example.com/data.json#/a/b")) ;; (:foo (:bar 1))
The URL fetcher should accept a URL string and return a JSON string. Note that the JSON parser is always applied, the default is json-parse-string
.
For example, using plz.el to fetch
(require 'plz)
;; custom URL fetcher
(let* ((plz-get-string (lambda (url) (plz 'get url)))
(my-remote (json-remote :url-fetcher plz-get-string)))
(jsonp-resolve-remote my-remote "https://example.com/data.json#/a/b"))
;; Note that the default JSON parser expects a string, so this will not work
(let* ((plz-get-string (lambda (url) (plz 'get url :as #'json-read))) ;; using the plz JSON parser
(my-remote (json-remote :url-fetcher plz-get-string)))
(jsonp-resolve-remote my-remote "https://example.com/data.json#/a/b")) ;; ERROR
The cache operates with custom URL fetchers too.
You can use a whitelist to restrict URL fetching to a single domain:
(let* ((my-domains '("https://foo.com/.*" "https://.*.foo.com"))
(my-remote (jsonp-remote :whitelist my-domains)))
(jsonp-resolve-remote my-remote "https://example.com/data.json#/a/b") ;; ERROR
(jsonp-resolve-remote my-remote "https://bar.foo.com/data.json#/a")) ;; OK
The whitelist is checked before calling the URL fetcher, so this will work even with a custom URL parser.
If you need to resolve pointers within $ref
objects, you can use the jsonp-nested-elt
function.
It is similar to map-nested-elt
in that it takes a list of keys, but it will resolve any $ref
properties as JSON pointers, and it will
signal an error if the keys don’t resolve.
For example, given this JSON
{
"$defs": {
"nonEmptyString": {
"type": "string",
"minLength": 1
}
},
"type": "object",
"properties": {
"name": {
"type": "object",
"properties": {
"firstName": { "$ref": "#/$defs/nonEmptyString" },
"lastName": { "$ref": "#/defs/nonEmptyString" }
}
}
}
}
resolve #/properties/name/properties/firstName/type
(jsonp-nested-elt the-json '(properties name properties firstName type)) ;; "string"
Note that the list of keys can include strings, symbols or numbers. Strings and symbols are automatically converted, so the following also works
(jsonp-nested-elt the-json '("properties" "name" "properties" "firstName" "type")) ;; "string"
Contributions are welcome!