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.
—
Core operations for finding, updating, and filtering data using JSONPath expressions. These operations preserve context information and provide full path tracking for located data elements.
JSONPath-NG preserves complete path information and context for all located data elements.
class DatumInContext:
"""Represents a datum along a path from a context."""
def __init__(self, value, path: JSONPath = None, context: 'DatumInContext' = None):
"""
Initialize datum with context.
Args:
value: The actual data value
path: JSONPath to this datum
context: Parent datum context
"""
@classmethod
def wrap(cls, data) -> 'DatumInContext':
"""Wrap raw data in DatumInContext if not already wrapped"""
def in_context(self, context, path) -> 'DatumInContext':
"""Place this datum within another context"""
@property
def full_path(self) -> JSONPath:
"""Complete path from root to this datum"""
@property
def id_pseudopath(self) -> JSONPath:
"""Path with IDs substituted when available"""
value: Any # The actual data value
path: JSONPath # JSONPath to this location
context: Optional['DatumInContext'] # Parent contextUsage example:
from jsonpath_ng import parse
data = {'users': [{'name': 'Alice', 'id': 1}, {'name': 'Bob', 'id': 2}]}
expr = parse('$.users[*].name')
matches = expr.find(data)
for match in matches:
print(f"Value: {match.value}")
print(f"Full path: {match.full_path}")
print(f"Context: {match.context.value}")Locate data elements matching JSONPath expressions.
class JSONPath:
def find(self, data) -> Iterable[DatumInContext]:
"""
Find all data elements matching this JSONPath expression.
Args:
data: JSON data structure to search
Returns:
Iterable of DatumInContext objects containing matching values and their paths
"""
def find_or_create(self, data) -> Iterable[DatumInContext]:
"""
Find matching elements, creating missing intermediate objects.
Args:
data: JSON data structure to search/modify
Returns:
Iterable of DatumInContext objects, creating empty objects for missing paths
"""Usage examples:
from jsonpath_ng import parse
data = {
'store': {
'book': [
{'title': 'Book 1', 'price': 10.99},
{'title': 'Book 2', 'price': 15.99}
]
}
}
# Find all book titles
title_expr = parse('$.store.book[*].title')
matches = title_expr.find(data)
titles = [match.value for match in matches] # ['Book 1', 'Book 2']
# Find with creation of missing paths
missing_expr = parse('$.store.magazine[*].title')
matches = missing_expr.find_or_create(data) # Creates empty magazine arrayModify data elements at JSONPath locations.
class JSONPath:
def update(self, data, val):
"""
Update all matching locations with val. Only updates existing paths.
Args:
data: JSON data structure to modify
val: New value to set, or callable(old_value, parent_dict, key) -> new_value
Returns:
Modified data structure (same object, modified in place)
"""
def update_or_create(self, data, val):
"""
Update matching locations with val, creating missing paths.
Args:
data: JSON data structure to modify
val: New value to set, or callable(old_value, parent_dict, key) -> new_value
Returns:
Modified data structure (same object, modified in place)
"""Usage examples:
from jsonpath_ng import parse
data = {'users': [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}]}
# Update all ages to 35
age_expr = parse('$.users[*].age')
updated_data = age_expr.update(data, 35)
# Update with function
def increment_age(old_val, parent, key):
return old_val + 1
updated_data = age_expr.update(data, increment_age)
# Update single user by index
single_expr = parse('$.users[0].name')
updated_data = single_expr.update(data, 'Alice Smith')
# Create missing path
missing_expr = parse('$.users[*].status')
updated_data = missing_expr.update_or_create(data, 'active')Remove data elements based on filter conditions.
class JSONPath:
def filter(self, fn, data):
"""
Filter matching elements using predicate function.
Args:
fn: Predicate function that returns True for elements to remove
data: JSON data structure to filter
Returns:
Filtered data structure with matching elements removed
"""Usage examples:
from jsonpath_ng import parse
data = {
'products': [
{'name': 'Widget A', 'price': 10.00, 'stock': 5},
{'name': 'Widget B', 'price': 15.00, 'stock': 0},
{'name': 'Widget C', 'price': 20.00, 'stock': 10}
]
}
# Remove out-of-stock products
expr = parse('$.products[*]')
filtered_data = expr.filter(lambda item: item['stock'] == 0, data)
# Remove expensive products
price_expr = parse('$.products[*].price')
filtered_data = price_expr.filter(lambda price: price > 15.00, data)
# Remove specific fields
name_expr = parse('$.products[*].name')
filtered_data = name_expr.filter(lambda x: True, data) # Removes all name fieldsConfigure automatic ID field generation for consistent object identification.
# Module-level configuration
import jsonpath_ng.jsonpath as jsonpath
jsonpath.auto_id_field: Optional[str] = None # Field name for auto-generated IDs
class AutoIdForDatum(DatumInContext):
"""
Special DatumInContext that generates automatic IDs based on path.
Used when auto_id_field is configured globally.
The value is always the path leading up to it (not including the ID field),
with any ID fields along the way replacing path segments.
"""
def __init__(self, datum, id_field: Optional[str] = None):
"""
Initialize auto-ID datum.
Args:
datum: Source DatumInContext
id_field: Field name for ID (defaults to global auto_id_field)
"""
@property
def value(self) -> str:
"""Auto-generated ID based on id_pseudopath of the datum"""
@property
def path(self) -> str:
"""ID field name"""
@property
def context(self) -> DatumInContext:
"""Original datum context"""
def in_context(self, context, path) -> 'AutoIdForDatum':
"""Place this auto-ID datum within another context"""
def __eq__(self, other) -> bool:
"""Compare with another AutoIdForDatum"""Usage example:
import jsonpath_ng.jsonpath as jsonpath
from jsonpath_ng import parse
# Enable auto-ID generation
jsonpath.auto_id_field = 'id'
data = {'users': [{'name': 'Alice'}, {'name': 'Bob'}]}
# Find auto-generated IDs
id_expr = parse('$.users[*].id')
matches = id_expr.find(data)
ids = [match.value for match in matches] # ['users.0', 'users.1']
# Mix with existing IDs
data = {'users': [{'name': 'Alice', 'id': 'alice'}, {'name': 'Bob'}]}
matches = id_expr.find(data)
ids = [match.value for match in matches] # ['alice', 'users.1']Helper functions for path manipulation and data structure management.
def _create_list_key(dict_: dict) -> list:
"""
Internal helper: Add list to dictionary and return it.
Used for list creation in update_or_create operations.
Args:
dict_: Dictionary to add list to
Returns:
Empty list that was added to dictionary
"""
def _clean_list_keys(struct_):
"""
Internal helper: Replace special list keys with actual lists.
Used to clean up data structures after update_or_create operations.
Args:
struct_: Data structure to clean
Returns:
Cleaned data structure with LIST_KEY placeholders replaced by lists
"""Navigate between related data elements using context information.
class Parent(JSONPath):
"""JSONPath that matches the parent node of the current match."""
def find(self, datum) -> List[DatumInContext]:
"""
Find parent context of current datum.
Args:
datum: Current DatumInContext
Returns:
List containing parent DatumInContext
Raises:
Error if no parent context exists
"""Usage example:
from jsonpath_ng import parse
data = {
'company': {
'departments': [
{'name': 'Engineering', 'employees': [{'name': 'Alice'}, {'name': 'Bob'}]},
{'name': 'Sales', 'employees': [{'name': 'Carol'}]}
]
}
}
# Find employee names and their department
expr = parse('$.company.departments[*].employees[*].name.`parent`.`parent`.name')
matches = expr.find(data)
dept_names = [match.value for match in matches] # ['Engineering', 'Engineering', 'Sales']# Module-level constants and configuration
NOT_SET: object # Sentinel value for missing data
LIST_KEY: object # Special key used internally for list operations
# Configuration variables
auto_id_field: Optional[str] = None # Enable automatic ID generationCommon error patterns and exception handling:
from jsonpath_ng import parse
from jsonpath_ng.exceptions import JsonPathParserError
try:
# Invalid JSONPath syntax
expr = parse('$.invalid[syntax')
except JsonPathParserError as e:
print(f"Parse error: {e}")
# Handle missing data gracefully
data = {'users': [{'name': 'Alice'}]}
expr = parse('$.users[*].age') # Age field doesn't exist
matches = expr.find(data) # Returns empty list, no exception
values = [match.value for match in matches] # []
# Safe updates
updated = expr.update(data, 25) # No-op, returns original data unchangedInstall with Tessl CLI
npx tessl i tessl/pypi-jsonpath-ng