A jsonpath implementation powered by treepath technology.
Project description
The jsonpath_tp Package.
The jsonpath_tp is a jsonpath implementation built on top of treepath technology. Jsonpath is query language for extracting data from json document. The jsonpath_tp attempts to follow the standard defined in jsonpath with the exceptions:
- script expression are not supported
- filter (script) expression define script differently in that a script is any single argument python function.
- filter expression support regular expression
The jsonpath_tp can be used programmatically or via th OS command line interface (CLI).
Quick Start Programmatically
All the jsonpath_tp components should be imported as follows:
from jsonpath_tp import get, find
A jsonpath example that gets c's value from json data.
data = {
"a": {
"b": [
{
"c": 1
},
{
"c": 2
}]
}
}
value = get("$.a.b[0].c", data)
assert value == 1
A jsonpath example that gets c's value from json data.
value = [value for value in find("$.a.b[*].c", data)]
assert value == [1, 2]
Quick Start Command Line Interface
To use the cli install the jsonpath_tp python package in a venv:
python -m venv venv
source bin venv/bin/activate
pip install jsonpath_tp
A jsonpath example that gets c's value from json data.
pass
Solar System Json Document
The examples shown in this README use the following json document. It describes our solar system. Click to expand.
solar_system = {...}
{
"star": {
"name": "Sun",
"diameter": 1391016,
"age": null,
"planets": {
"inner": [
{
"name": "Mercury",
"Number of Moons": "0",
"diameter": 4879,
"has-moons": false
},
{
"name": "Venus",
"Number of Moons": "0",
"diameter": 12104,
"has-moons": false
},
{
"name": "Earth",
"Number of Moons": "1",
"diameter": 12756,
"has-moons": true
},
{
"name": "Mars",
"Number of Moons": "2",
"diameter": 6792,
"has-moons": true
}
],
"outer": [
{
"name": "Jupiter",
"Number of Moons": "79",
"diameter": 142984,
"has-moons": true
},
{
"name": "Saturn",
"Number of Moons": "82",
"diameter": 120536,
"has-moons": true
},
{
"name": "Uranus",
"Number of Moons": "27",
"diameter": 51118,
"has-moons": true
},
{
"name": "Neptune",
"Number of Moons": "14",
"diameter": 49528,
"has-moons": true
}
]
}
}
}
query examples.
Description | Xpath | jsonpath | treepath |
---|---|---|---|
Find planet earth. | /star/planets/inner[name='Earth'] | $.star.planets.inner[?(@.name=='Earth')] | path.star.planets.inner[*][?(@.name == 'Earth')] |
List the names of all inner planets. | /star/planets/inner[*].name | $.star.planets.inner[*].name | path.star.planets.inner[*].name |
List the names of all planets. | /star/planets/*/name | $.star.planets.[].name | path.star.planets.wc[*].name |
List the names of all celestial bodies | //name | $..name | path.rec.name |
List all nodes in the tree Preorder | //* | $.. | path.rec |
Get the third rock from the sun | /star/planets/inner[3] | $.star.planets.inner[2] | path.star.planets.inner[2] |
List first two inner planets | /star/planets.inner[position()<3] | $.star.planets.inner[:2] | path.star.planets.inner[0:2] |
$.star.planets.inner[0, 1] | path.star.planets.inner[0, 2] | ||
List planets smaller than earth | /star/planets/inner[diameter < 1] | $.star.planets.inner[?(@.diameter < 12756)] | path.star.planets.inner[wc][has(path.diameter < 12756)] |
List celestial bodies that have planets. | //*[planets]/name | $..[?(@.planets)].name | path.rec[?(@.planets)].name |
List the planets with more than 50 moons | $..[?(int(@['Number of Moons']) > 50)].name | path.rec[wc][has(path['Number of Moons'] > 50, int)].name |
Traversal Functions
get
The get function returns the first value the path leads to.
Get the star name from the solar_system
sun = get("$.star.name", solar_system)
assert sun == 'Sun'
When there is no match, MatchNotFoundError is thrown.
try:
get("$.star.human_population", solar_system)
assert False, "Not expecting humans on the sun"
except MatchNotFoundError:
pass
Or if preferred, a default value can be given.
human_population = get("$.star.human_population", solar_system, default=0)
assert human_population == 0
In addition to a constant, the default value may also be a callable
def population():
return 0
human_population = get("$.star.human_population", solar_system, default=population)
assert human_population == 0
The default value can be automatically injected in to json document
human_population = get("$.star.human_population", solar_system, default=1, store_default=True)
assert human_population == solar_system['star']["human_population"]
find
The find function returns an Iterator that iterates to each value the path leads to. Each value is determine on its iteration.
Find All the planet names.
inner_planets = [planet for planet in find("$.star.planets.inner[*].name", solar_system)]
assert inner_planets == ['Mercury', 'Venus', 'Earth', 'Mars']
Tracing Debugging
All the functions: get, find, support tracing. An option, when enabled, records the route the algorithm takes to determine a match.
This example logs the route the algorithm takes to find the inner planets. The print function is give to capture the logs, but any single argument function can be used.
inner_planets = [planet for planet in find("$.star.planets.inner[*].name", solar_system, trace=log_to(print))]
assert inner_planets == ['Mercury', 'Venus', 'Earth', 'Mars']
The results
"""
at $.star got {'name': 'Sun', 'dia...
at $.star.planets got {'inner': [{'name': ...
at $.star.planets.inner got [{'name': 'Mercury',...
at $.star.planets.inner[*] got {'name': 'Mercury', ...
at $.star.planets.inner[0].name got 'Mercury'
at $.star.planets.inner[*] got {'name': 'Venus', 'N...
at $.star.planets.inner[1].name got 'Venus'
at $.star.planets.inner[*] got {'name': 'Earth', 'N...
at $.star.planets.inner[2].name got 'Earth'
at $.star.planets.inner[*] got {'name': 'Mars', 'Nu...
at $.star.planets.inner[3].name got 'Mars'
"""
Path
The root
The $ point to root of the tree.
value = get("$", solar_system)
assert value == solar_system
In a filter, the @ point to the current element.
value = get("$.star[?(@ == 'Sun')]", solar_system)
assert value == 'Sun'
Dictionaries
Keys
The dictionary keys are referenced as dynamic attributes on a path.
inner_from_attribute = get("$.star.planets.inner", solar_system)
inner_from_string_keys = get("$.['star']['planets']['inner']", solar_system)
assert inner_from_attribute == inner_from_string_keys == solar_system['star']['planets']['inner']
Keys With Special Characters
Dictionary keys that are not valid python syntax can be referenced as quoted strings.
sun_equatorial_diameter = get("$.star.planets.inner[0]['Number of Moons']", solar_system)
assert sun_equatorial_diameter == solar_system['star']['planets']['inner'][0]['Number of Moons']
Wildcard as a Key.
The * attribute specifies all sibling keys. It is useful for iterating over attributes.
star_children = [child for child in find("$.star.*", solar_system)]
assert star_children == [solar_system['star']['name'],
solar_system['star']['diameter'],
solar_system['star']["age"],
solar_system['star']['planets'], ]
Comma Delimited Keys
Multiple dictionary keys can be specified using a comma delimited list.
last_and_first = [planet for planet in find("$.star['diameter', 'name']", solar_system)]
assert last_and_first == [1391016, "Sun"]
List
Indexes
List can be access using index.
earth = get("$.star.planets.inner[2]", solar_system)
assert earth == solar_system['star']['planets']['inner'][2]
List the third inner and outer planet.
last_two = [planet for planet in find("$.star.*.*[2].name", solar_system)]
assert last_two == ['Earth', 'Uranus']
Comma Delimited Indexes.
List indexes can be specified as a comma delimited list.
last_and_first = [planet for planet in find("$.star.planets.outer[3, 0].name", solar_system)]
assert last_and_first == ["Neptune", "Jupiter"]
Slices
List can be access using slices.
List the first two planets.
first_two = [planet for planet in find("$.star.planets.outer[:2].name", solar_system)]
assert first_two == ["Jupiter", "Saturn"]
List the last two planets.
last_two = [planet for planet in find("$.star.planets.outer[-2:].name", solar_system)]
assert last_two == ["Uranus", "Neptune"]
List all outer planets in reverse.
last_two = [planet for planet in find("$.star.planets.outer[::-1].name", solar_system)]
assert last_two == ["Neptune", "Uranus", "Saturn", "Jupiter"]
List the last inner and outer planets.
last_two = [planet for planet in find("$.star.*.*[-1:].name", solar_system)]
assert last_two == ["Mars", "Neptune"]
Wildcard as an Index.
The * token can be used as a list index. It is useful for iterating over attributes.
all_outer = [planet for planet in find("$.star.planets.outer[*].name", solar_system)]
assert all_outer == ["Jupiter", "Saturn", "Uranus", "Neptune"]
Recursion
The .. double dot implies recursive search. It executes a preorder tree traversal. The search algorithm descends the tree hierarchy evaluating the path on each vertex until a match occurs. On each iteration it continues where it left off. This is an example that finds all the planets names.
all_planets = [p for p in find("$.star.planets..name", solar_system)]
assert all_planets == ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
Here is another example that finds all the celestial bodies names.
all_celestial_bodies = [p for p in find("$..name", solar_system)]
assert all_celestial_bodies == ['Sun', 'Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus',
'Neptune']
Filters
Filters are use to add additional search criteria.
filter
The ?() is a filter that evaluates a branched off path relative to its parent path. This example finds all celestial bodies that have planets.
sun = get("$..[?(@.planets)].name", solar_system)
assert sun == "Sun"
This search finds all celestial bodies that have a has-moons attribute.
all_celestial_bodies_moon_attribute = [planet for planet in find("$..[?(@['has-moons'])].name", solar_system)]
assert all_celestial_bodies_moon_attribute == ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus',
'Neptune']
This search finds all celestial bodies that have moons. Note the operator.truth is used to exclude planets that don't have moons.
operator_truth = operator.truth
all_celestial_bodies_moon_attribute = [planet for planet in
find("$..[?(operator_truth(@['has-moons']))].name",
solar_system,
locals=locals()
)]
assert all_celestial_bodies_moon_attribute == ['Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
has filter comparison operators
Filters can be specified with a comparison operator.
earth = [planet for planet in find("$..[?(@.diameter == 12756)].name", solar_system)]
assert earth == ['Earth']
earth = [planet for planet in find("$..[?(@.diameter != 12756)].name", solar_system)]
assert earth == ['Sun', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
earth = [planet for planet in find("$..[?(@.diameter > 12756)].name", solar_system)]
assert earth == ['Sun', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
earth = [planet for planet in find("$..[?(@.diameter >= 12756)].name", solar_system)]
assert earth == ['Sun', 'Earth', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
earth = [planet for planet in find("$..[?(@.diameter < 12756)].name", solar_system)]
assert earth == ['Mercury', 'Venus', 'Mars']
earth = [planet for planet in find("$..[?(@.diameter <= 12756)].name", solar_system)]
assert earth == ['Mercury', 'Venus', 'Earth', 'Mars']
There is also a regular expression operator. This example finds all the planets that end with the letter s.
earth = [planet for planet in find(r"$..[?(@.name =~ '\w+s')].name", solar_system)]
assert earth == ['Venus', 'Mars', 'Uranus']
has filter type conversion
Sometimes the value is the wrong type for the comparison operator. In this example the attribute 'Number of Moons' is str type.
planets = [planet for planet in find("$..[?(@['Number of Moons'] > '5')].name", solar_system)]
assert planets == ['Jupiter', 'Saturn']
This is how to convert the type to an int before applying the comparison operator.
planets = [planet for planet in find("$..[?(int(@['Number of Moons']) > 5)].name", solar_system)]
assert planets == ['Jupiter', 'Saturn', 'Uranus', 'Neptune']
has filter comparison operators as single argument functions
A filter operator can be specified as a single argument function. Here an example that searches for planets that have the same diameter as earth.
earths_diameter = partial(operator.eq, 12756)
earth = [planet for planet in find("$..[?(earths_diameter(@.diameter))].name", solar_system, locals=locals())]
assert earth == ['Earth']
Any single argument function can be used as an operator. This example uses a Regular Expression to finds planets that end with the letter s.
name_ends_with_s = re.compile(r"\w+s").match
earth = [planet for planet in find("$..[?(name_ends_with_s(@.name))].name", solar_system, locals=locals())]
assert earth == ['Venus', 'Mars', 'Uranus']
This example uses a closure to find planets that have the same diameter as earth.
def smaller_than_earth(value):
return value < 12756
earth = [planet for planet in find("$..[?(smaller_than_earth(@.diameter))].name", solar_system, locals=locals())]
assert earth == ['Mercury', 'Venus', 'Mars']
logical and, or and not filters
and
A regular express to test if the second letter in the value is 'a'.
second_letter_is_a = re.compile(r".a.*").fullmatch
The and function evaluates as the logical and operator. It is equivalent to: (arg1 and arg2 and ...)
found = [planet for planet in find("$..[?(@.diameter < 10000 and (second_letter_is_a(@.name)))].name",
solar_system,
locals=locals())
]
assert found == ['Mars']
or
The or function evaluates as the logical or operator. It is equivalent to: (arg1 and arg2 and ...)
found = [planet for planet in find("$..[?(@.diameter < 10000 or (second_letter_is_a(@.name)))].name",
solar_system,
locals=locals()
)
]
assert found == ['Mercury', 'Earth', 'Mars', 'Saturn']
not
The not function evaluates as the logical not operator. It is equivalent to: (not arg) This example find all the planets names not not equal to Earth. Note the double nots.
found = [planet for planet in find("$..[?(not (@.name != 'Earth'))].name", solar_system)]
assert found == ['Earth']
Combining has, and, or, and not filters.
Each of the has function can be passed as arguments to any of the other has function to construct complex boolean equation. This example is equivalent to: (10000 > diameter or diameter > 20000) and second_letter_is_a(name))
found = [planet for planet in
find("$..[?((@.diameter < 10000 or @.diameter > 20000) and (second_letter_is_a(@.name)))].name",
solar_system,
locals=locals()
)
]
assert found == ['Mars', 'Saturn']
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for jsonpath_tp-0.0.0.dev4-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0d083256fb1c99c80a3333772a3cc581c9c5b36d21d3566de7c09bb44e279547 |
|
MD5 | d190848e1ebbff86596cf03dd49a1418 |
|
BLAKE2b-256 | 921083f4f1b6051be433e6299084a65e90d9ca332da9796079ae6050bc5b88e8 |