Arbitrary variables can be made available to filter expressions using the filter_context argument to findall() and finditer(). filter_context should be a mapping of strings to JSON-like objects, like lists, dictionaries, strings and integers.
Filter context variables are selected using a filter query starting with the filter context identifier, which defaults to _ and has usage similar to $ and @.
import jsonpath
data = {
"users": [
{
"name": "Sue",
"score": 100,
},
{
"name": "John",
"score": 86,
},
{
"name": "Sally",
"score": 84,
},
{
"name": "Jane",
"score": 55,
},
]
}
user_names = jsonpath.findall(
"$.users[?@.score < _.limit].name",
data,
filter_context={"limit": 100},
)Add, remove or replace filter functions by updating the function_extensions attribute of a JSONPathEnvironment. It is a regular Python dictionary mapping filter function names to any callable, like a function or class with a __call__ method.
Section 2.4.1 of RFC 9535 defines a type system for function expressions and requires that we check that filter expressions are well-typed. With that in mind, you are encouraged to implement custom filter functions by extending jsonpath.function_extensions.FilterFunction, which forces you to be explicit about the types of arguments the function extension accepts and the type of its return value.
!!! info
[`FilterFunction`](api.md#jsonpath.function_extensions.FilterFunction) was new in Python JSONPath version 0.10.0. Prior to that we did not enforce function expression well-typedness. To use any arbitrary [callable](https://docs.python.org/3/library/typing.html#typing.Callable) as a function extension - or if you don't want built-in filter functions to raise a `JSONPathTypeError` for function expressions that are not well-typed - set [`well_typed`](api.md#jsonpath.JSONPathEnvironment.well_typed) to `False` when constructing a [`JSONPathEnvironment`](api.md#jsonpath.JSONPathEnvironment).
As an example, we'll add a min() filter function, which will return the minimum of a sequence of values. If any of the values are not comparable, we'll return the special undefined value instead.
from typing import Iterable
import jsonpath
from jsonpath.function_extensions import ExpressionType
from jsonpath.function_extensions import FilterFunction
class MinFilterFunction(FilterFunction):
"""A JSONPath function extension returning the minimum of a sequence."""
arg_types = [ExpressionType.VALUE]
return_type = ExpressionType.VALUE
def __call__(self, value: object) -> object:
if not isinstance(value, Iterable):
return jsonpath.UNDEFINED
try:
return min(value)
except TypeError:
return jsonpath.UNDEFINED
env = jsonpath.JSONPathEnvironment()
env.function_extensions["min"] = MinFilterFunction()
example_data = {"foo": [{"bar": [4, 5]}, {"bar": [1, 5]}]}
print(env.findall("$.foo[?min(@.bar) > 1]", example_data))Now, when we use env.finall(), env.finditer() or env.compile(), our min function will be available for use in filter expressions.
$..products[?@.price == min($..products.price)]
The built-in functions can be removed from a JSONPathEnvironment by deleting the entry from function_extensions.
import jsonpath
env = jsonpath.JSONPathEnvironment()
del env.function_extensions["keys"]Or aliased with an additional entry.
import jsonpath
env = jsonpath.JSONPathEnvironment()
env.function_extensions["properties"] = env.function_extensions["keys"]Alternatively, you could subclass JSONPathEnvironment and override the setup_function_extensions method.
from typing import Iterable
import jsonpath
class MyEnv(jsonpath.JSONPathEnvironment):
def setup_function_extensions(self) -> None:
super().setup_function_extensions()
self.function_extensions["properties"] = self.function_extensions["keys"]
self.function_extensions["min"] = min_filter
def min_filter(obj: object) -> object:
if not isinstance(obj, Iterable):
return jsonpath.UNDEFINED
try:
return min(obj)
except TypeError:
return jsonpath.UNDEFINED
env = MyEnv()Calls to type-aware function extension are validated at JSONPath compile-time automatically. If well_typed is set to False or a custom function extension does not inherit from FilterFunction, its arguments can be validated by implementing the function as a class with a __call__ method, and a validate method. validate will be called after parsing the function, giving you the opportunity to inspect its arguments and raise a JSONPathTypeError should any arguments be unacceptable. If defined, validate must take a reference to the current environment, an argument list and the token pointing to the start of the function call.
def validate(
self,
env: JSONPathEnvironment,
args: List[FilterExpression],
token: Token,
) -> List[FilterExpression]:It should return an argument list, either the same as the input argument list, or a modified version of it. See the implementation of the built-in match function for an example.
Python JSONPath can be customized by subclassing JSONPathEnvironment and overriding class attributes and/or methods. Then using findall(), finditer() and compile() methods of that subclass.
The default identifier tokens, like $ and @, can be changed by setting attributes a on JSONPathEnvironment. This example sets the root token (default $) to be ^.
import JSONPathEnvironment
class MyJSONPathEnvironment(JSONPathEnvironment):
root_token = "^"
data = {
"users": [
{"name": "Sue", "score": 100},
{"name": "John", "score": 86},
{"name": "Sally", "score": 84},
{"name": "Jane", "score": 55},
],
"limit": 100,
}
env = MyJSONPathEnvironment()
user_names = env.findall(
"^.users[?@.score < ^.limit].name",
data,
)This table shows all available identifier token attributes.
| attribute | default |
|---|---|
| filter_context_token | _ |
| keys_token | # |
| root_token | $ |
| self_token | @ |
By default, we accept both Python and C-style logical operators in filter expressions. That is, not and ! are equivalent, and and && are equivalent and or and || are equivalent. You can change this using class attributes on a Lexer subclass and setting the lexer_class attribute on a JSONPathEnvironment.
This example changes all three logical operators to strictly match the JSONPath spec.
from jsonpath import JSONPathEnvironment
from jsonpath import Lexer
class MyLexer(Lexer):
logical_not_pattern = r"!"
logical_and_pattern = r"&&"
logical_or_pattern = r"\|\|"
class MyJSONPathEnvironment(JSONPathEnvironment):
lexer_class = MyLexer
env = MyJSONPathEnvironment()
env.compile("$.foo[?@.a > 0 && @.b < 100]") # OK
env.compile("$.foo[?@.a > 0 and @.b < 100]") # JSONPathSyntaxErrorThe non-standard keys selector is used to retrieve the keys/properties from a JSON Object or Python mapping. It defaults to ~ and can be changed using the keys_selector_token attribute on a JSONPathEnvironment subclass.
This example changes the keys selector to *~.
from jsonpath import JSONPathEnvironment
class MyJSONPathEnvironment(JSONPathEnvironment):
keys_selector_token = "*~"
data = {
"users": [
{"name": "Sue", "score": 100},
{"name": "John", "score": 86},
{"name": "Sally", "score": 84},
{"name": "Jane", "score": 55},
],
"limit": 100,
}
env = MyJSONPathEnvironment()
print(env.findall("$.users[0].*~", data)) # ['name', 'score']Python JSONPath limits the minimum and maximum JSON array or Python sequence indices (including slice steps) allowed in a JSONPath query. The default minimum allowed index is set to -(2**53) + 1, and the maximum to (2**53) - 1. When a limit is reached, a JSONPathIndexError is raised.
You can change the minimum and maximum allowed indices using the min_int_index and max_int_index attributes on a JSONPathEnvironment subclass.
from jsonpath import JSONPathEnvironment
class MyJSONPathEnvironment(JSONPathEnvironment):
min_int_index = -100
max_int_index = 100
env = MyJSONPathEnvironment()
query = env.compile("$.users[999]")
# jsonpath.exceptions.JSONPathIndexError: index out of range, line 1, column 8TODO:
TODO:
TODO:
TODO:
TODO: