Skip to main content

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 support 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) with the addition of jsonpath_cli with package.

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]

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jsonpath_tp-1.0.0.tar.gz (19.4 kB view hashes)

Uploaded Source

Built Distribution

jsonpath_tp-1.0.0-py3-none-any.whl (17.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page