A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming.
—
Extended JSONPath functionality including arithmetic operations, advanced filtering with comparison operators, string manipulation functions, and iterable operations. Extensions are available through the jsonpath_ng.ext module.
Enhanced parser with support for arithmetic, filtering, and additional operators.
def parse(path: str, debug: bool = False) -> JSONPath:
"""
Parse JSONPath string with extended syntax support.
Args:
path: JSONPath expression string with extended syntax
debug: Enable debug output
Returns:
JSONPath object supporting extended operations
"""Usage example:
from jsonpath_ng.ext import parse
# Arithmetic operations
expr = parse('$.products[*].price + $.products[*].tax')
# Filtering with comparisons
expr = parse('$.users[?(@.age > 18)]')
# String operations
expr = parse('$.text.`split(",", *, -1)`')Perform mathematical operations on JSONPath results.
class Operation(JSONPath):
"""Arithmetic operations on JSONPath expressions."""
def __init__(self, left, op: str, right):
"""
Initialize arithmetic operation.
Args:
left: Left operand (JSONPath or literal value)
op: Operator ('+', '-', '*', '/')
right: Right operand (JSONPath or literal value)
"""
def find(self, datum) -> List[DatumInContext]:
"""
Perform arithmetic operation and return results.
Returns:
List of computed values, empty list if operation fails
"""Usage examples:
from jsonpath_ng.ext import parse
data = {
'products': [
{'price': 10.00, 'tax': 1.00},
{'price': 20.00, 'tax': 2.00}
]
}
# Add price and tax
expr = parse('$.products[*].price + $.products[*].tax')
results = expr.find(data)
totals = [r.value for r in results] # [11.00, 22.00]
# Multiply by constant
expr = parse('$.products[*].price * 1.1')
results = expr.find(data)
marked_up = [r.value for r in results] # [11.00, 22.00]
# String concatenation
data = {'user': {'first': 'John', 'last': 'Doe'}}
expr = parse('$.user.first + " " + $.user.last')
result = expr.find(data)[0].value # "John Doe"
# Array operations (same length required)
data = {'scores': {'math': [85, 90], 'english': [80, 95]}}
expr = parse('$.scores.math[*] + $.scores.english[*]')
results = expr.find(data)
totals = [r.value for r in results] # [165, 185]# Arithmetic operators
OPERATOR_MAP = {
'+': operator.add, # Addition/concatenation
'-': operator.sub, # Subtraction
'*': operator.mul, # Multiplication
'/': operator.truediv # Division
}Advanced filtering with comparison operators and boolean logic.
class Filter(JSONPath):
"""JSONQuery filter for array elements."""
def __init__(self, expressions):
"""
Initialize filter.
Args:
expressions: List of filter expression objects
"""
def find(self, datum) -> List[DatumInContext]:
"""
Apply filter expressions to array elements.
Returns:
List of elements that match all filter expressions
"""
class Expression(JSONPath):
"""Filter expression with comparison operator."""
def __init__(self, left, op: Optional[str], right):
"""
Initialize filter expression.
Args:
left: Left JSONPath expression
op: Comparison operator ('==', '!=', '<', '>', '<=', '>=', '=~')
right: Right operand (literal value)
"""
def find(self, datum) -> List[DatumInContext]:
"""
Evaluate expression against datum.
Returns:
List containing datum if expression is true, empty otherwise
"""Usage examples:
from jsonpath_ng.ext import parse
data = {
'employees': [
{'name': 'Alice', 'age': 30, 'dept': 'Engineering'},
{'name': 'Bob', 'age': 25, 'dept': 'Sales'},
{'name': 'Carol', 'age': 35, 'dept': 'Engineering'},
{'name': 'Dave', 'age': 28, 'dept': 'Marketing'}
]
}
# Filter by age
expr = parse('$.employees[?(@.age > 30)]')
results = expr.find(data)
names = [r.value['name'] for r in results] # ['Carol']
# Filter by equality
expr = parse('$.employees[?(@.dept == "Engineering")]')
results = expr.find(data)
engineers = [r.value['name'] for r in results] # ['Alice', 'Carol']
# Filter by inequality
expr = parse('$.employees[?(@.age != 25)]')
results = expr.find(data)
not_25 = [r.value['name'] for r in results] # ['Alice', 'Carol', 'Dave']
# Regex matching
data = {'users': [{'email': 'alice@example.com'}, {'email': 'bob@test.org'}]}
expr = parse('$.users[?(@.email =~ ".*@example\\.com")]')
results = expr.find(data)
example_users = [r.value['email'] for r in results] # ['alice@example.com']
# Multiple conditions with AND
expr = parse('$.employees[?(@.age > 25 & @.dept == "Engineering")]')
results = expr.find(data)
senior_engineers = [r.value['name'] for r in results] # ['Alice', 'Carol']# Comparison operators
OPERATOR_MAP = {
'!=': operator.ne, # Not equal
'==': operator.eq, # Equal
'=': operator.eq, # Equal (alternative)
'<=': operator.le, # Less than or equal
'<': operator.lt, # Less than
'>=': operator.ge, # Greater than or equal
'>': operator.gt, # Greater than
'=~': regex_match # Regex match (strings only)
}String manipulation functions for text processing.
class Split(JSONPath):
"""String splitting operation."""
def __init__(self, signature: str):
"""
Initialize split operation from signature.
Args:
signature: Function signature like "split(sep, segment, maxsplit)"
"""
def find(self, datum) -> List[DatumInContext]:
"""
Split string value and return specified segment.
Returns:
List containing split result or empty list if operation fails
"""Usage examples:
from jsonpath_ng.ext import parse
data = {'text': 'apple,banana,cherry,date'}
# Split and get all segments
expr = parse('$.text.`split(",", *, -1)`')
result = expr.find(data)[0].value # ['apple', 'banana', 'cherry', 'date']
# Split and get specific segment
expr = parse('$.text.`split(",", 1, -1)`')
result = expr.find(data)[0].value # 'banana'
# Split with maxsplit
expr = parse('$.text.`split(",", *, 2)`')
result = expr.find(data)[0].value # ['apple', 'banana', 'cherry,date']
# Split on whitespace
data = {'sentence': 'The quick brown fox'}
expr = parse('$.sentence.`split(" ", 2, -1)`')
result = expr.find(data)[0].value # 'brown'class Sub(JSONPath):
"""String substitution with regex."""
def __init__(self, signature: str):
"""
Initialize substitution operation.
Args:
signature: Function signature like "sub(pattern, replacement)"
"""
def find(self, datum) -> List[DatumInContext]:
"""
Perform regex substitution on string value.
Returns:
List containing substituted string or empty list if operation fails
"""Usage examples:
from jsonpath_ng.ext import parse
data = {'text': 'Hello World 123'}
# Simple substitution
expr = parse('$.text.`sub("World", "Universe")`')
result = expr.find(data)[0].value # 'Hello Universe 123'
# Regex substitution with capture groups
expr = parse('$.text.`sub("([a-zA-Z]+) ([a-zA-Z]+) (\\\\d+)", "\\\\3 \\\\2 \\\\1")`')
result = expr.find(data)[0].value # '123 World Hello'
# Remove digits
expr = parse('$.text.`sub("\\\\d+", "")`')
result = expr.find(data)[0].value # 'Hello World 'class Str(JSONPath):
"""Convert value to string."""
def __init__(self, signature: str):
"""
Initialize string conversion.
Args:
signature: Function signature like "str()"
"""
def find(self, datum) -> List[DatumInContext]:
"""
Convert datum value to string.
Returns:
List containing string representation
"""Usage example:
from jsonpath_ng.ext import parse
data = {'number': 42, 'boolean': True, 'null_val': None}
# Convert number to string
expr = parse('$.number.`str()`')
result = expr.find(data)[0].value # '42'
# Convert boolean to string
expr = parse('$.boolean.`str()`')
result = expr.find(data)[0].value # 'True'Operations for working with arrays and objects as collections.
class Len(JSONPath):
"""Get length of iterable."""
def find(self, datum) -> List[DatumInContext]:
"""
Get length of array, object, or string.
Returns:
List containing length as integer
"""Usage examples:
from jsonpath_ng.ext import parse
data = {
'array': [1, 2, 3, 4, 5],
'object': {'a': 1, 'b': 2, 'c': 3},
'string': 'hello'
}
# Array length
expr = parse('$.array.`len`')
result = expr.find(data)[0].value # 5
# Object length (number of keys)
expr = parse('$.object.`len`')
result = expr.find(data)[0].value # 3
# String length
expr = parse('$.string.`len`')
result = expr.find(data)[0].value # 5class Keys(JSONPath):
"""Get object keys."""
def find(self, datum) -> List[DatumInContext]:
"""
Get keys of object as array.
Returns:
List containing array of object keys
"""Usage example:
from jsonpath_ng.ext import parse
data = {'user': {'name': 'Alice', 'age': 30, 'city': 'New York'}}
expr = parse('$.user.`keys`')
result = expr.find(data)[0].value # ['name', 'age', 'city']class Path(JSONPath):
"""Get current path as string."""
def find(self, datum) -> List[DatumInContext]:
"""
Get JSONPath to current location as string.
Returns:
List containing path string
"""Usage example:
from jsonpath_ng.ext import parse
data = {'users': [{'name': 'Alice'}, {'name': 'Bob'}]}
expr = parse('$.users[*].name.`path`')
results = expr.find(data)
paths = [r.value for r in results] # ['$.users[0].name', '$.users[1].name']class SortedThis(JSONPath):
"""Sort array elements."""
def __init__(self, sorts: Optional[List] = None):
"""
Initialize sorting operation.
Args:
sorts: List of (path, reverse) tuples for sorting criteria
"""
def find(self, datum) -> List[DatumInContext]:
"""
Sort array elements by specified criteria.
Returns:
List of sorted elements
"""Usage examples:
from jsonpath_ng.ext import parse
data = {
'numbers': [3, 1, 4, 1, 5, 9, 2, 6],
'users': [
{'name': 'Charlie', 'age': 35},
{'name': 'Alice', 'age': 30},
{'name': 'Bob', 'age': 25}
]
}
# Simple sort
expr = parse('$.numbers.`sorted`')
result = expr.find(data)[0].value # [1, 1, 2, 3, 4, 5, 6, 9]
# Sort objects by field (ascending)
expr = parse('$.users[\\age]')
results = expr.find(data)
sorted_users = [r.value for r in results] # Sorted by age ascending
# Sort by field (descending)
expr = parse('$.users[/age]')
results = expr.find(data)
sorted_users = [r.value for r in results] # Sorted by age descending
# Sort by multiple fields
expr = parse('$.users[\\name, /age]') # Sort by name asc, then age desc
results = expr.find(data)
sorted_users = [r.value for r in results]class ExtendedJsonPathLexer(JsonPathLexer):
"""Extended lexer with additional tokens for extensions."""
literals = JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-']
tokens = ['BOOL', 'FILTER_OP', 'SORT_DIRECTION', 'FLOAT'] + JsonPathLexer.tokens
def t_BOOL(self, t) -> Token:
"""Parse boolean literals (true/false)"""
def t_SORT_DIRECTION(self, t) -> Token:
"""Parse sort direction indicators (/ for desc, \\ for asc)"""
def t_FLOAT(self, t) -> Token:
"""Parse floating point numbers"""
class ExtentedJsonPathParser(JsonPathParser):
"""Extended parser supporting arithmetic, filtering, and string operations."""
def __init__(self, debug: bool = False, lexer_class=None):
"""
Initialize extended parser.
Args:
debug: Enable debug output
lexer_class: Custom lexer class (defaults to ExtendedJsonPathLexer)
"""class DefintionInvalid(Exception):
"""Raised when string operation definition syntax is invalid"""Extended operations include additional error cases:
from jsonpath_ng.ext import parse
# Arithmetic type errors
data = {'text': 'hello', 'number': 42}
expr = parse('$.text + $.number') # String + int
results = expr.find(data) # Returns empty list, no exception
# Division by zero
data = {'a': 10, 'b': 0}
expr = parse('$.a / $.b')
results = expr.find(data) # Returns empty list
# Invalid regex
try:
expr = parse('$.text.`sub("[invalid", "replacement")`')
results = expr.find({'text': 'hello'})
except Exception as e:
print(f"Regex error: {e}")
# Filter on non-array
data = {'user': {'name': 'Alice'}}
expr = parse('$.user[?(@.name == "Alice")]') # user is not an array
results = expr.find(data) # Returns empty listInstall with Tessl CLI
npx tessl i tessl/pypi-jsonpath-ng