CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-jsonpath-ng

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.

Pending
Overview
Eval results
Files

path-operations.mddocs/

Path Operations

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.

Capabilities

Data Context Management

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 context

Usage 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}")

Finding Data

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 array

Updating Data

Modify 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')

Filtering Data

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 fields

Automatic ID Generation

Configure 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']

Path Utilities

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
    """

Advanced Path Navigation

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']

Global Configuration

# 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 generation

Error Handling

Common 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 unchanged

Install with Tessl CLI

npx tessl i tessl/pypi-jsonpath-ng

docs

command-line.md

core-parsing.md

extensions.md

index.md

path-operations.md

tile.json