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

extensions.mddocs/

Extensions

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.

Core Extensions

Extended Parser

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

Arithmetic Operations

Perform mathematical operations on JSONPath results.

Basic Arithmetic

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]

Supported Operators

# Arithmetic operators
OPERATOR_MAP = {
    '+': operator.add,     # Addition/concatenation
    '-': operator.sub,     # Subtraction  
    '*': operator.mul,     # Multiplication
    '/': operator.truediv  # Division
}

Filtering Operations

Advanced filtering with comparison operators and boolean logic.

Filter Expressions

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

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

String manipulation functions for text processing.

String Splitting

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'

String Substitution

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 '

String Conversion

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'

Iterable Operations

Operations for working with arrays and objects as collections.

Length Operation

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

Keys Operation

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

Path Operation

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

Sorting Operation

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]

Extended Parser Configuration

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

Extension Exceptions

class DefintionInvalid(Exception):
    """Raised when string operation definition syntax is invalid"""

Error Handling

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 list

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