JSON Pointer
The JSONPointer
class is an implementation of the
RFC 6901 JSON Pointer specification. In jschon, JSONPointer
is commonly used to represent paths to nodes within JSON
and JSONSchema
documents, and to extract nodes from
those documents. But JSONPointer
is designed to work
with any JSON-compatible Python object – including, for example, native dict
and list
objects.
Let’s see how JSONPointer
works. We begin with an import:
>>> from jschon import JSON, JSONPointer
Now, consider the following example. This JSON
instance describes
how characters in JSON object keys must be escaped in order to form reference tokens
within an RFC 6901 JSON pointer string:
>>> escape_rule = JSON([
... {"~": "~0"},
... {"/": "~1"}
... ])
Nodes within this instance can be referenced in the usual way, as described in the JSON guide:
>>> escape_rule[1]["/"]
JSON('~1')
If we look at the path
property of this node, we see
that the string representation of the JSON pointer includes the escaped form of
the "/"
key:
>>> escape_rule[1]["/"].path
JSONPointer('/1/~1')
Now let’s get that path into a variable and inspect it a little more closely:
>>> slash_path = escape_rule[1]["/"].path
A JSONPointer
is a Sequence[str]
, with each
item in the sequence being the unescaped object key or array index at the next
node down the path to the referenced value:
>>> [key for key in slash_path]
['1', '/']
You can create a list
or tuple
of keys directly from a
JSONPointer
instance:
>>> tuple(slash_path)
('1', '/')
Notice that the array index is represented as a string, too. In fact, it
matches the key
attribute on the corresponding
JSON
node:
>>> escape_rule[1].key
'1'
To extract the referenced object from a JSON document, we use the
evaluate()
method:
>>> slash_path.evaluate(escape_rule)
JSON('~1')
So far, we’ve seen how to work with the JSONPointer
instance that appears as the path
of a JSON
node. Now let’s look at how to construct a JSONPointer
.
Consider the following example document:
>>> doc = {"a": {"b": {"c": {"d": "😎"}}}}
The obvious way to make a JSONPointer
that points
to the leaf node in this example would be:
>>> JSONPointer('/a/b/c/d')
JSONPointer('/a/b/c/d')
Then, as we’d expect:
>>> JSONPointer('/a/b/c/d').evaluate(doc)
'😎'
But here are a few alternative ways to create the same JSON pointer:
>>> JSONPointer(['a', 'b', 'c', 'd'])
JSONPointer('/a/b/c/d')
>>> JSONPointer(['a'], '/b/c', ['d'])
JSONPointer('/a/b/c/d')
>>> JSONPointer('/a/b', JSONPointer('/c/d'))
JSONPointer('/a/b/c/d')
As you can see, the JSONPointer
constructor accepts
– and concatenates – any number of arguments. Each argument can be either:
an RFC 6901 JSON pointer string (with reserved characters escaped); or
an iterable of unescaped keys or array indices – which may itself be a
JSONPointer
instance.
A special case is the JSONPointer
constructed without
any args:
>>> JSONPointer()
JSONPointer('')
This represents a reference to an entire document:
>>> JSONPointer().evaluate(doc)
{'a': {'b': {'c': {'d': '😎'}}}}
The /
operator provides a convenient way to extend a JSONPointer
:
>>> JSONPointer() / 'a' / ('b', 'c', 'd')
JSONPointer('/a/b/c/d')
>>> JSONPointer('/a/b') / JSONPointer('/c/d')
JSONPointer('/a/b/c/d')
It works by copying the left-hand operand (a JSONPointer
instance) and appending the right-hand operand (an unescaped key, or an iterable
of unescaped keys). Note that JSONPointer
is immutable,
so each invocation of /
produces a new JSONPointer
instance.
As a Sequence
, JSONPointer
supports getting
a key by its index:
>>> JSONPointer('/a/b/c/d')[-4]
'a'
And taking a slice into a JSONPointer
returns a new
JSONPointer
instance composed of the specified slice
of the original’s keys:
>>> JSONPointer('/a/b/c/d')[1:-1]
JSONPointer('/b/c')