jsonpath

Contains utilities for performing complex searches in nested Python

iterables based on both keys/indices and values. This has been tested (using gorp.test.test_jsonpath) on Python 3.6-3.9. It might work on 3.5, but no warranty is made except for 3.6-3.9.

The most immediately comparable project in terms of scope is jsonpath_ng,

which is fully JsonPath standard-compliant, but which appears to me to lack some of this module’s power, such as regex matching on keys.

Some things that you can do with this module:
  • Use regular expressions to match keys as well as values in dicts.

  • Filter layers of JSON based on comparisons of multiple values (e.g.,

    get indices 1:10 of an array only if arr[1] > str(arr[2]))

  • Mutate JSON at the nodes found by a search.

  • Filter pandas DataFrames using the same query language, while still

    getting their incredibly fast performance.

  • If you prefer not to use a query parser, build layers of filters by an

    object-oriented approach. See the Filter, GlobalConstraint, Mutator, and JsonPath classes. Thanks to jsonpath_ng for pioneering this approach!

  • Build arithmetic expressions of the values and keys in an iterable

    without using eval(). See math_eval, and its workhorse function, compute(eqn).

FUNCTIONS

gorp.jsonpath.json_extract(filter_path, json, get_full_path_also=False, reverse_selectivity=False, recursive=False, fuzzy_keys=False, sub_func=None, ask_permission=True)
json: an arbitrarily nested Python object, where each layer

is a tuple, list, dict, or subclass of dict.

filter_path: list of Filter steps, or single string or number.

The preferred form of filter_path is a query string that can be parsed into a list of Filter layers by parse_json_path.

get_full_path_also: bool, see “Returns”.

reverse_selectivity: bool, see “Returns”.

recursive: bool, see “Returns”.

fuzzy_keys: If True, you can test keys with regular expressions and many kinds of mathematical tests. The drawback of using fuzzy_keys is that arrays and dicts have to be searched exhaustively in linear time, rather than just going straight to an exact index or key in constant time. fuzzy_keys can be toggled by ‘zz’ in a filter_path query string.

sub_func: Function of one variable. If this is not None, after the JSON is extracted, the sub_func is applied to the terminal nodes by mutate_json. You can also turn on “sub mode” in a query string by terminating the query string with “~~ss”, followed by a string representing the sub_func that can be parsed by math_eval.compute().

ask_permission: only relevant if sub_func is not None. When in “sub mode”, if ask_permission is True, create an interactive prompt.

Returns: a list of the children that can be found by paths matched by this function.

Notes on optional parameters:
  • If reverse_selectivity is True at a given filter layer, the matching

changes as follows:
  • If fuzzy_keys is False, only the matching on values is reversed.

  • If fuzzy_keys is True, the matching on both values and keys is

reversed.

  • If get_full_path_also is True, return instead a dict mapping

complete paths (tuples of keys and indices) to the children of those paths. * If recursive is True, we keep descending until we find a match, even if the match fails on the first level it’s applied to.

EXAMPLES

>>> bad_json = {'b': 3,
   '6': 7,
   'jub': {'uy': [1, 2, 3],
           'yu': [6, {'y': 'b', 'M8': 9, 1: (3,0)}]}}
>>> json_extract(json = bad_json,
    filter_path = '^\d+$')
    # this is a regex match on keys; it doesn't work by default
ALERT: It looks like you may have tried to match keys with regular expressions
['\\d'] while not in fuzzy_keys mode. Did you intend this?
[]
>>> json_extract(json = bad_json,
    filter_path = '^\d+$', fuzzy_keys=True)
    # regexes on keys work with fuzzy_keys=True.
[7]
>>> json_extract(json = bad_json,
    filter_path = 'jub~~@yu~~uy')
    # look for the children of key 'uy' that's a child of
    # root-level 'jub', but only if 'jub' also has the child 'yu'.
    # This is non-fuzzy matching, but it works since 'jub', 'yu'
    # and 'uy' all match keys in json exactly.
[[1, 2, 3]]
>>> json_extract(json = bad_json,
    filter_path = '..~~@zz^[a-z]{2}$~~@zznn0')
    # 'zz' toggles fuzzy_keys; the first use turned it on
    # and the second turned it off
[1, 6]
>>> json_extract(json = bad_json,
    fuzzy_keys = True,
    filter_path = '[a-z]{2}~~@nn0')
    # Matches 'jub', but then tries to find index 0 in graph['jub'] and can't
[]
>>> json_extract(json = bad_json,
    filter_path = '..~~@zz[5-8];;nn5:9',
    get_full_path_also = True)
    # recursively ('..') looks for keys with a substring in {'5','6','7','8'}
    # OR (';;') that have the numeric value in range(5,9) ('nn5:9')
{('jub', 'yu', 1, 'M8'): 9, ('6',): 7}
>>> json_extract(json = bad_json,
    filter_path = '[5-8];;nn5:9')
    # careful! you're trying to match an IntRange and a regex to keys when not
    # fuzzy_keys. When fuzzy_keys = False, IntRanges only match array indices.
[]
>>> json_extract(json = bad_json,
    filter_path = '..~~@nnint(k)*2<4vvnnstr(v)>`8`',
    get_full_path_also = True)
    # If fuzzy_keys is False, we can't use functions like "int(k)*2 < 4"
    # to constrain keys. As a result, we get this error:
Traceback (most recent call last):
...
JsonPathError: When not in fuzzy_keys mode, only IntRange slicers and ints are allowed as key tests.
>>> small_json = [[1, 2, 3, 4, 5], {0: 'a', 1: 'b'},
        {2: 'c'}, [6, 7], {3: 'd'}]
>>> json_extract(json = small_json,
        filter_path = 'nn:3~~@..~~@ggnn1vvx[0]<x[1]',
        get_full_path_also = True)
        # The 'gg' string before the third layer means that we create a
        # GlobalConstraint that matches the key/index 1,
        # if itbl[0] < itbl[1].
{(0, 1): 2, (1, 1): 'b'}
>>> json_extract(json = small_json,
        filter_path = 'nn:3~~@..~~@ggnn1:3vvnnx[0]<x[1]',
        get_full_path_also = True)
        # GlobalConstraints only match IntRanges to array indices.
        # IntRanges do not match numeric dict keys in a GlobalConstraint.
{(0, 1): 2, (0, 2): 3}
>>> json_extract(json = bad_json,
    filter_path = '..~~@zz(?i)^[M-Y]||vvnn1:10:2',
    get_full_path_also = True)
    # The '||' means that at least one key-value pair must match the key
    # constraint OR the value constraint. It doesn't have to match both.
{('jub', 'uy'): [1, 2, 3],
 ('jub', 'yu'): [6, {'y': 'b', 'M8': 9, 1: (3, 0)}],
 ('6',): 7,
 ('b',): 3}
>>> json_extract(json = bad_json,
    filter_path = 'zz!!\d~~@!!yu',
    get_full_path_also = True)
    # in this case, '!!' toggles reverse_selectivity on and then off again.
{('jub', 'yu'): [6, {'y': 'b', 'M8': 9, 1: (3, 0)}]}
>>> json_extract(json = bad_json,
    fuzzy_keys = True,
    filter_path = '..~~@(?i)[a-z]+\d~~^^',
    get_full_path_also = True)
    # '~~^^' means "find the grandparents of the current container".
    # You could also get the parents with '~~^' or the great-grandparents
    # with '~~^^^', and so on.
{('jub',): {'uy': [1, 2, 3], 'yu': [6, {'y': 'b', 'M8': 9, 1: (3, 0)}]}}
>>> json_extract(json = {'a':17, 'b':2, 'c': 4, 'd': 31},
    filter_path = 'zz^[a-z]vvnnx>3&&str(x)<`3`',
    get_full_path_also=True)
    # Each value must satisfy BOTH constraints separated by the '&&'.
    # So the key has to start with a lowercase ASCII letter,
    # and the value has to have a numeric value greater than 3 ("x>3") AND
    # a string value less than '3' ("str(x)<`3`).
{('a',): 17}
>>> json_extract(json = {'b': 3,  'g6': 2,  '6': 7},
    filter_path = "ggvvnnstr(x[`b`]) =~ `^\d`~~zz\dvvnnx<4")
    # The "=~" operator allows regex matching within a compute expression.
[2]
>>> baseball = {'foo':
...    {'alice': {'hits': [3, 4, 2, 5], 'at-bats': [4, 3, 3, 6]},
...     'bob': {'hits': [-2, 0, 4, 6], 'at-bats': [1, 3, 5, 6]}},
... 'bar':
...    {'carol': {'hits': [7, 3, 0, 5], 'at-bats': [8, 4, 6, 6]},
...     'dave': {'hits': [1, 0, 4, 10], 'at-bats': [1, 3, 6, 11]}}}
>>> json_extract(json = baseball,
...              filter_path = "zz~~@~~@.*AGGsumBY0",
...              # get the sum of everything, grouped by the first level
...              # of organization (in this case the team)
...              get_full_path_also = True)
{('bar',): 75, ('foo',): 53}
>>> json_extract(json = baseball,
...              filter_path = "zz~~@~~@.*AGGsumBY1:",
...              get_full_path_also = True)
{('carol', 'at-bats'): 24, ('carol', 'hits'): 15, ('dave', 'at-bats'): 21, ('dave', 'hits'): 15, ('alice', 'at-bats'): 16, ('alice', 'hits'): 14, ('bob', 'at-bats'): 15, ('bob', 'hits'): 8}
gorp.jsonpath.parse_json_path(x, fuzzy_keys=False, ask_about_ambiguity=True)

x: a string.

fuzzy_keys: See the same argument of json_extract.__doc__.

ask_about_ambiguity: Print possibly helpful messages when x appears to have a

malformed pipe, a missing ‘nn’ for declaring “compute mode”, or a missing ‘zz’ for declaring fuzzy_keys mode when using regexes.

RULES FOR PARSING AN ENTIRE JSON PATH (‘x’):

  • x is split into path layers or substeps by one of the delimiters

{‘~~’, ‘~~@’, ‘~~^+’), ‘~~}’}.

  • ‘~~’ means that the preceding path substep serves as a “check”;

(de/a)scent to a child or parent cannot proceed unless all checks at a path layer are satisfied. There can be any number of checks at a path layer.

  • ‘~~@’ comes at the end of a path layer; it means that once all the

checks are satisfied at the current path layer, you begin searching the CHILDREN of all nodes that satisfied the preceding check.

  • ‘~~^+’ also comes at the end of a path layer; it means that

once all the checks are satisfied at the current path layer, you begin searching the n^th ancestor of the current level of json, where n is the number of ‘^’ characters.

  • ‘~~}’ can be supplied as the last three characters of a path.

It means that if the all the checks in the current layer of json were satisfied, you return the CURRENT LAYER, not children or ancestors.

  • Descent-type-search (as specified by ‘~~@’) is the default, so the

final path layer will always return the children of all matched nodes unless the path is terminated with ‘~~}’ or ‘~~^+’.

  • ‘..’ switches json_extract into recursive search mode.

Recursive search mode is off by default.

  • ‘!!’ as its own path layer or at the beginning of another path layer

is treated as a special indicator that toggles reverse_selectivity mode.

  • ‘zz’ at the beginninng of another path layer toggles fuzzy_keys, which

changes the fuzzy_keys parameter of the Filters created by this function.

  • ‘gg’ at any point in a path layer (including on a “check” filter in a

multi-substep path layer) creates a GlobalConstraint rather than a Filter.

  • ‘~~ss<func of one variable>’ as the FINAL ELEMENT IN A PATH

(even after ‘~~}’) ends the path with a Mutator object that can be used to perform in-place transformations on the JSON after paths have been found by json_extract() or JsonPath.extract().

  • ‘AGG<func of one iterable variable>(BY<integer|IntRange>)’ as the

FINAL ELEMENT IN A PATH (even after ‘~~}’) ends the path with a Aggregator object that can be used to aggregate the data found by json_extract or JsonPath.extract().

RULES FOR PARSING A SINGLE PATH SUBSTEP (performed by parse_json_path_step):

  • ‘nn’ at any point in a path substep toggles “compute mode”.

While the parser is in “compute mode”, it interprets everything as a mathematical expression, and passes it into math_eval.compute(). Thus, it is easier to include string filters at the beginning of a tuple path substep and numeric filters at the end of a tuple path substep.

  • ‘;;’ separates two elements within the path substep.

  • ‘&&’ separates two elements within the path substep, and every

consecutive element joined by ‘&&’ is bundled together; so ‘cond1;;cond2&&cond3;;cond4’ would filter on cond1 OR (cond2 AND cond3) OR cond4.

  • ‘vv’ is a delimiter.

everything after ‘vv’ in the path substep is a filter on VALUES and everything before ‘vv’ is a filter on KEYS.

  • Note that ‘vv’ resets the ‘nn’ flag.

This means that you need to use ‘nn’ to turn on compute mode separately for values and keys. * Note also that for a GlobalConstraint, everything after the ‘vv’ flag is a filter on the iterable as a whole.

  • By default, a path substep must match at least one key filter AND at

least one value filter, assuming both key and value filters are supplied.

  • ‘||’ is an optional indicator that can be used with ‘vv’.

When ‘||’ is supplied, the filter at that path substep only requires that a key filter OR a value filter is matched.

  • The ‘||’ flag does not do anything when used on a GlobalConstraint.

  • When not in compute mode, all strings (including numeric strings) are

treated as regular expressions if fuzzy_keys = True and not inside a GlobalConstraint. Otherwise, they are treated as plain strings.

EXAMPLES:

>>> parse_json_path("nn5:8vv77")
[[Filter(keys_in = [IntRange(5, 8, 1)], vals_in = ['77'], key_or_val = False, action = 'down', fuzzy_keys = False)]]
>>> parse_json_path("\d\n~~}") # don't forget to turn on fuzzy_keys if you want regex!
ALERT: It looks like you may have tried to match keys with regular expressions
['\d'] while not in fuzzy_keys mode. Did you intend this?
[[Filter(keys_in = ['\d\n'], vals_in = [], key_or_val = False, action = 'stay', fuzzy_keys = False)]]
>>> parse_json_path("zzx*3<=4**3")
ALERT: It looks like you may have forgotten to use the 'nn' token to indicate that the equation(s)
['3<=4'] should be treated as math expressions. Did you intend this?
[[Filter(keys_in = ['x*3<=4**3'], vals_in = [], key_or_val = False, action = 'down', fuzzy_keys = True)]]
>>> parse_json_path("^[a-z]{2}$||vvnnx<3&&x>2;;nn^[23]", fuzzy_keys = True)
    # the "nnx<3&&x>2" means that the conditions "x<3" and "x>2" must BOTH be satisfied.
    # The other condition, "^[23]", wants the value to be a string starting with '2' or
    # '3'.
    # So long as BOTH of the first two conditions OR the third condition is/are satisfied,
    # this vals_in requirement is met.
[[Filter(keys_in = ["^[a-z]{2}$"], vals_in = [[compute('x<3'), compute('x>2')], "^[23]"], key_or_val = True, action = 'down', fuzzy_keys = True)]]
>>> parse_json_path("3~~ggnn1:4:2vvnnx[0]>1")
[[Filter(keys_in = ['3'], vals_in = [], key_or_val = False, action = 'check', fuzzy_keys = False), GlobalConstraint(keys_in = [IntRange(1, 4, 2)], vals_in = [compute('x[0]>1')], action = 'down')]]
>>> parse_json_path('..~~@yu~~zzvvuy~~^^')
['..', [Filter(keys_in = ['yu'], vals_in = [], key_or_val = False, action = 'check', fuzzy_keys = False)], [Filter(keys_in = [], vals_in = ['uy'], key_or_val = False, action = 'up2', fuzzy_keys = True)], [Filter(keys_in = [''], vals_in = [], key_or_val = False, action = 'down', fuzzy_keys = True)]]
>>> parse_json_path('a~~@nn1:~~}~~ss str(x)')
[[Filter(keys_in = ['a'], vals_in = [], key_or_val = False, action = 'down', fuzzy_keys = False)], [Filter(keys_in = [IntRange(1, inf, 1)], vals_in = [], key_or_val = False, action = 'stay', fuzzy_keys = False)], Mutator(replacement_func = 'compute( str(x))')]
gorp.jsonpath.mutate_json(obj, path, func, ask_permission=False, **kwargs)

Mutates a nested iterable object in-place by applying func to obj[path].

obj: an object containing arbitrarily nested iterables.

path: a tuple of keys or indices to follow through the object.

func: a function to apply to the child found at the end of the path, OR a non-function that everything should be replaced by, regardless of its value.

ask_permission: if True, ask before changing the node. Useful when this is called repeatedly by other functions.

Returns: None.

gorp.jsonpath.mutate_json_repeatedly(obj, paths, func, ask_permission=False)

See mutate_json. This applies the func to obj[path] for each path in paths.

LOANER FUNCTION FROM math_eval

math_eval.compute(eqn, safe=False)
Evaluate a string encoding a mathematical expression of any number of variables

(including no variables).

safe: bool. If True, eqn can only contain numbers, arithmetic, comparisons, and the

unary functions ‘float’, ‘int’, and ‘not’. All iterables are forbidden in safe mode. Also, a function produced by compute() with safe = True will raise a ValueError if supplied any non-numeric inputs.

Notes:
  • ‘e’, ‘pi’, ‘inf’, ‘True’, and ‘False’ are constants with the values

    you’d expect. - You can add more constants using the “constants” dict.

  • Variable names may contain only ASCII letters.

  • Do not declare variable names; they is determined at runtime.

  • Square brackets for array and dict access can be used as in normal Python.

  • Returns a function (if variable names were included) or a scalar otherwise.

  • All arithmetic and comparison operators are allowed, as are nested parentheses,

    as are the logical ‘^’ (xor), ‘&’ (and), and ‘|’ operators.

  • IntRange() objects (which act like builtin range() objects, but are treated as

    equal to all integers in the range as well as containing all of them) can be declared in a compute() expression by the same start:stop:step syntax that is used to declare a slice in normal Python.

  • IntRanges can be used inside array-slicing square brackets in the same way as

    a normal slice.

  • IntRanges support the ‘==’ operation and no other arithmetic operations.

  • You can declare a string literal inside a compute expression by surrounding the

    string in ‘``’ backticks. ‘``’ characters can be included inside the string by escaping them with ‘'. - If you want arbitrarily nested string literals (e.g., something like

    before a backtick for each level of nesting of that backtick. So in the compute expression “x+`3+int(`7`+`float(\`4\`)`)`”, “3+int(7`+`float(`4`))” is a string on level 1, “7” and “float(4)” are strings on level 2, and ‘4’ is a string on level 3.

  • Chained comparisons are evaluated left-to-right, unlike in base Python.

  • The logical ‘^’, ‘|’, and ‘&’ operators are evaluated AFTER the comparison

    operators, unlike in base Python where the logical operators are evaluated first.

  • The following function names are reserved functions of one argument in compute():
    • int, float, str, len, sum, tuple, values, not, min, max, sorted.

    • These all do the same things as in normal Python (‘values’ means dict.values).

    • Unlike in base Python, the “not” function requires parens; so “not(expr)”.

    • Note that since you can’t define arrays of numbers in a compute() expression,

      you can’t actually use “sum” on anything other than arrays passed in as variables.

    • You can easily add new functions that take one variable as an argument

      by adding new name-function pairs to the “ufunctions” dict that is defined above.

    • An unfortunate corrolary of the above observation is that if you added enough

      ufunctions, you could undoubtedly find a way to do something dangerous with compute().

    • You cannot specify a key or the “reversed” parameter for “sorted(x)”.

  • “map” is also a reserved function of two arguments.
    • fun map itbl” works very much like map(func, itbl) in base Python; it also

      returns a generator expression applying a function to everything in itbl.

    • Note that the proper syntax is “x map y”, not map(x,y), because my parser

      does not support the latter method for calling binary operators.

    • Also, eqn must be a compute expression of one variable or a function name in

      ufunctions. Because eqn is a string literal within the context of the compute() expression, eqn must be backtick-enclosed.

  • “=~” is a reserved function of two arguments, both strings.
    • string =~ pat” is equivalent to (re.search(pat, string) is not None).

  • “sub” is a reserved function that accepts two arguments.
    • The proper syntax is “<string/varname> sub “<regex>//<replacement>”

    • The above is equivalent to re.sub(<regex>, <replacement>, <string/varname>)

  • The arithmetic and logic binops work fine for pandas Series and numpy arrays,

    but the float, str, and int functions only work on scalars. - To change the type of a numpy array or pandas Series, use the “intar”,

    “floatar”, and “strar” functions to change to the type before the “ar”.

CLASSES

class gorp.jsonpath.JsonPath(filters_or_query, json=None, fuzzy_keys=False)

Class for manipulating and traversing filter paths.

Initialization args:

filters_or_query: a list of layers of Filters and GlobalConstraints; or a query that can be parsed by parse_json_path and translated into such a list.

json: a nested Python object. This can be added later by add_json().

fuzzy_keys: See the documentation for the Filter class and json_extract().

Attributes:

filters: A list of layers of Filters and GlobalConstraints, along with ‘..’ and ‘!!’ flags.

mutator: The final item in the “filters” list passed in when initialized. This is a Mutator object that mutates the JSON passed in to this func.

curLayer: Tracks what index of the filters the JsonPath will start traversing from. This can be manipulated with JsonPath.descend() to see how the resultset changes.

Methods:

‘+’: Two JsonPaths can be added together to concatenate their filters; the JsonPath produced by (a+b) has a’s JSON. You can also add a JsonPath to a list of filters, or vice versa.

copy(): Returns a new JsonPath with no associated json.

descend(): Allows examination of how each filter layer changes the resultset.

extract(): Like json_extract().

sub(): Finds all the paths from extract(), then applies a function to each child node found. This is an in-place transformation.

Other notes:

You can access slices and individual filter layers in a JsonPath with the [a:b] or [a] array-slicing notation.

gorp.jsonpath.JsonPath.descend(self, layers=None)

layers: The number of layers of this JsonPath’s filters to traverse.

If layers is None, just use all of the Filters and GlobalConstraints in this JsonPath.

Returns None, but updates the JsonPath’s resultset so that repeated calls to descend() followed by peeks at its resultset show you how its resultset evolves as the filters are successively applied.

Note that the traversal starts at self.curLayer, which may not be the first layer.

EXAMPLES:

>>> jpath_ex = JsonPath('a~~@nn1:', json={'a':[1,2,3],'b':2})
>>> jpath_ex.resultset
>>> jpath_ex.curLayer
0
>>> jpath_ex.descend(1)
    # applies the filter(s) in layer 0, and descends to layer 1.
>>> jpath_ex.resultset
{('a',): [1, 2, 3]}
>>> jpath_ex.descend(1) # applies the filters in layer 1, which is the last layer
>>> jpath_ex.resultset
{('a', 1): 2, ('a', 2): 3}
gorp.jsonpath.JsonPath.extract(self, layers=None)

With no arguments, this is equivalent to json_extract(self.json). If layers is an int, it’s equivalent to :func:`json_extract`(self.json, self.filters[:layers]), unless one or more layers is ‘..’ or ‘!!’, because ‘..’ and ‘!!’ don’t count as their own filter layers.

gorp.jsonpath.JsonPath.sub(self, func=None, ask_permission=False, layers=None)
Applies func to the terminal nodes of all the paths found by

self.extract(layers). If func is None, uses this JsonPath’s mutator to alter the JSON.

See the documentation for mutate_json() and JsonPath.extract(). WARNING: This is an IN-PLACE transformation. If you’re concerned about making unintended changes, use ask_permission=True.

PERFORMANCE NOTE: If you intend to use the results from extract() and then use sub(), the below will save time by not calling extract() twice:

>>> results = jp.extract() # jp is a JsonPath
>>> mutate_json_repeatedly(jp.json, results.keys(), jp.mutator.replacement_func)

EXAMPLES:

>>> jpath_ex = JsonPath('a~~@nn1:', json={'a':[1,2,3],'b':2})
>>> jpath_ex.sub(lambda x: (x+1)*2, False)
>>> jpath_ex.json
{'a':[1,6,8], 'b':2}
>>> jpath_ex.sub(lambda x: (x+1)*2, True)
At path ('a', 1),
6
    would be replaced by
14.
Do you want to do this? (y/n) y
There are 1 nodes remaining that may be replaced. Do you still want to be asked permission? (y/n) y
At path ('a', 2),
8
    would be replaced by
18.
Do you want to do this? (y/n) n
Do you want to quit? (y/n) y
>>> JsonPath('b~~@nn1:', json = jpath_ex.json).sub(lambda x: x*3, False, 1)
    # note that we supplied layers = 1, so this will only traverse the first filter
    # layer before trying to make replacements.
>>> jpath_ex.json
{'a':[1, 14, 8], 'b':6}
>>> JsonPath('a~~@nn1:~~ss str(x)',
...          json = jpath_ex.json).sub(ask_permission=False, layers = 2)
    # Here we are using the syntax of parse_json_path that allows us to add a Mutator
    # to a JsonPath by terminating the JsonPath string with "~~ss<func of 1 variable>"
>>> jpath_ex.json
{'a':[1, '14', '8'], 'b':6}
class gorp.jsonpath.Filter(keys_in=(), vals_in=(), key_or_val=False, action='down', fuzzy_keys=False)
Used to check keys, array indices, and values in nested Python objects.

Note that in this context “keys” means both dict keys and array indices.

Arguments:

keys_in: Function that performs a comparison, IntRange, number, string, or tuple that contains any number of nums, strings, comparison funcs, math_eval.IntRange, or tuples. If this is a tuple, a key that matches anything in the tuple is a match. Any tuple within the tuple represents a set of conditions that are bundled and must be simultaneously satisfied. If this is empty, all keys are matched.

vals_in: Same as keys_in, but tests values in a key-value pair.

key_or_val: Bool, default False. If true, any key-value pair where the key

matches keys_in OR vals_in is considered a match.

action: str: {‘check’, ‘down’, ‘stay’, ‘up’+str(integer)} This determines how json_extract treats key-value pairs that are matched by this filter.

  • If the action is ‘check’, there must be another filter in the filter

layer. This filter only makes assertions. * If the action is ‘down’, json_extract will search downwards (to the children) of any keys that are matched by this filter. * If the action is ‘stay’, json_extract will return the container holding the keys that match this filter. * If the action is ‘up’+str(integer), json_extract will search the integer^th ancestor (e.g., 1 = parent, 3 = great-grandparent) of the key matched by this filter.

fuzzy_keys: bool. If False, IntRanges match array indices but not dict keys and numbers and strings in keys_in must match keys and indices exactly. If this is True, keys can be tested “fuzzily” by comparison functions and IntRanges, and all strings are treated as regular expressions. Value testing is “fuzzy” regardless of the value of fuzzy_keys.

Other attributes:

keyfuncs: List of functions that perform key matching based on keys_in. valfuncs: List of functions that perform value matching based on vals_in.

Methods:

in: The test “(k, v) in filt” will return True if the key-value pair k,v matches the filter “filt”.

callable: “filt(k, v)” returns the same thing as “(k, v) in filt”.

class gorp.jsonpath.GlobalConstraint(keys_in=(), vals_in=(), action='down')

Whereas the Filter class checks individual key-value pairs in an array or dict, the GlobalConstraint is for applying constraints to the whole iterable.

keys_in: a list/tuple of strings, numbers, or IntRanges.

As with a Filter object with fuzzy_keys = False, each string/num in keys_in must exactly match at least one key or index in the iterable; no regular expressions!

vals_in: a list of functions that each take a single iterable as an argument.

These can be generated by the compute() function (which supports array and dict slicing and indexing) or they can just be normal Python functions. Unlike the keyfuncs and valfuncs of a Filter object, constraints can operate on multiple key-value pairs in a single iterable, like “itbl[‘b’]>=itbl[‘a’]” or “sum(itbl)>0”.

action: See the description of the “action” argument for Filter objects.

class math_eval.IntRange(*args)
A class like a builtin range() object that is treated as equaling all

integers in its range as well as containing them.

Like the builtin range, it is iterable. If the stop is +/-infinity,

iteration will stop at self.start - 3,141,592 or self.start + 3,141,592.

IntRanges have a slice(arr) method that returns a slice of arr with indices

from the IntRange.

NOTE: IntRange.fromstring(“::x”), where x is a negative integer, will NOT have a slice attribute that is equivalent to the slice produced by the “::x” syntax in normal Python.

The best way to build an IntRange that slices an array in reverse order starting from the last index is to use IntRange.fromstring(“-1::x”) where x is a negative integer.