Error Handling¶
Comprehensive guide to error handling in jsonatapy.
Error Types¶
All jsonatapy errors are raised as Python ValueError exceptions with descriptive messages.
Parse Errors¶
Raised when JSONata expression syntax is invalid.
import jsonatapy
try:
expr = jsonatapy.compile("invalid [[ syntax")
except ValueError as e:
print(f"Parse error: {e}")
# Parse error: Unexpected token at position 9
Common causes: - Unmatched brackets or parentheses - Invalid operators - Malformed expressions - Incorrect syntax
Evaluation Errors¶
Raised when expression evaluation fails.
import jsonatapy
try:
result = jsonatapy.evaluate("$undefined_func()", {})
except ValueError as e:
print(f"Evaluation error: {e}")
# Evaluation error: Unknown function: undefined_func
Common causes: - Unknown function names - Invalid function arguments - Accessing undefined variables - Runtime type mismatches
Guardrail Errors¶
Raised when an evaluation exceeds a configured timeout, max_stack_depth, or
max_sequence_length (see Guardrails). Each carries a distinct error code
so callers can tell which limit was hit.
import jsonatapy
try:
jsonatapy.evaluate(
"($inf := function(){$inf()}; $inf())",
None,
timeout=100,
)
except ValueError as e:
print(e)
# D1012: Evaluation timeout after 100 milliseconds. Check for infinite loop
try:
jsonatapy.evaluate(
"($f := function($n){$n = 0 ? 0 : 1 + $f($n - 1)}; $f(100))",
None,
max_stack_depth=50,
)
except ValueError as e:
print(e)
# D1011: Stack overflow. Check for non-terminating recursive function.
# Consider rewriting as tail-recursive
try:
jsonatapy.evaluate(
"$map([1..1000000], function($n) { $n })",
None,
max_sequence_length=1000,
)
except ValueError as e:
print(e)
# D2015: The maximum sequence length of 1000 was exceeded.
Codes:
- D1011 — recursion stack depth exceeded max_stack_depth
- D1012 — evaluation ran longer than timeout milliseconds
- D2015 — a query-result sequence (map/filter/wildcard/descendants/etc) exceeded max_sequence_length
All three default to unlimited (None) — these errors only occur if you've explicitly configured
the corresponding limit.
Note: U1001 and U1002 are separate, always-on native-stack safety nets (not configurable)
that guard against a whole-process crash on pathologically deep recursion or expression nesting —
they exist independently of any guardrail you configure and cannot be disabled.
Type Errors¶
Raised when operations receive incompatible types.
import jsonatapy
try:
result = jsonatapy.evaluate("$sum('not a number')", {})
except ValueError as e:
print(f"Type error: {e}")
# Type error: Argument 1 of function "sum" must be an array
Common causes: - Wrong argument types for functions - Invalid operations on incompatible types - Type conversion failures
Common Errors¶
Syntax Errors¶
Unmatched Brackets¶
# Error: Unmatched bracket
jsonatapy.compile("items[price > 100")
# ValueError: Expected ']' at end of expression
# Correct
jsonatapy.compile("items[price > 100]")
Invalid Operators¶
# Error: Invalid operator
jsonatapy.compile("items => price")
# ValueError: Unexpected token '=>' at position 6
# Correct
jsonatapy.compile("items ~> $map(function($v) { $v.price })")
Malformed Path Expressions¶
# Error: Invalid path
jsonatapy.compile("..items")
# ValueError: Unexpected token '..' at position 0
# Correct
jsonatapy.compile("items")
Function Errors¶
Unknown Function¶
# Error: Function doesn't exist
jsonatapy.evaluate("$myFunc()", {})
# ValueError: Unknown function: myFunc
# Use built-in functions
jsonatapy.evaluate("$uppercase('hello')", {})
Wrong Argument Count¶
# Error: Too few arguments
jsonatapy.evaluate("$substring('hello')", {})
# ValueError: The substring function requires at least 2 arguments
# Provide required arguments
jsonatapy.evaluate("$substring('hello', 0, 2)", {})
Wrong Argument Type¶
# Error: Wrong type
jsonatapy.evaluate("$sum('not an array')", {})
# ValueError: Argument 1 of function "sum" must be an array
# Pass correct type
jsonatapy.evaluate("$sum([1, 2, 3])", {})
Path Errors¶
Non-existent Fields¶
# Returns undefined (None in Python)
result = jsonatapy.evaluate("missing.field", {})
print(result) # None
# Use default value pattern
result = jsonatapy.evaluate("missing.field ? missing.field : 'default'", {})
print(result) # "default"
Type Mismatch in Path¶
# Cannot access property of non-object
data = {"value": 123}
jsonatapy.evaluate("value.property", data)
# Returns None (undefined behavior)
# Check type first
jsonatapy.evaluate("$type(value) = 'number' ? value : value.property", data)
JSON Parsing Errors¶
When using evaluate_json():
import jsonatapy
expr = jsonatapy.compile("items[price > 100]")
# Error: Invalid JSON
try:
result = expr.evaluate_json("not valid json")
except ValueError as e:
print(f"JSON error: {e}")
# JSON error: expected value at line 1 column 1
# Valid JSON
import json
json_str = json.dumps({"items": []})
result = expr.evaluate_json(json_str)
Error Handling Patterns¶
Basic Try-Except¶
import jsonatapy
def safe_evaluate(expression, data):
"""Safely evaluate with error handling."""
try:
return jsonatapy.evaluate(expression, data)
except ValueError as e:
print(f"JSONata error: {e}")
return None
Separate Parse and Evaluation Errors¶
import jsonatapy
def compile_and_evaluate(expression, data):
"""Separate compilation and evaluation errors."""
try:
expr = jsonatapy.compile(expression)
except ValueError as e:
print(f"Syntax error in expression: {e}")
return None
try:
return expr.evaluate(data)
except ValueError as e:
print(f"Evaluation error: {e}")
return None
Validation Before Evaluation¶
import jsonatapy
def validate_expression(expression):
"""Check if expression is syntactically valid."""
try:
jsonatapy.compile(expression)
return True, None
except ValueError as e:
return False, str(e)
# Usage
is_valid, error = validate_expression("items[price > 100]")
if is_valid:
result = jsonatapy.evaluate("items[price > 100]", data)
else:
print(f"Invalid expression: {error}")
Default Value on Error¶
import jsonatapy
def evaluate_with_default(expression, data, default=None):
"""Evaluate with default value on error."""
try:
return jsonatapy.evaluate(expression, data)
except ValueError:
return default
# Usage
result = evaluate_with_default("missing.field", {}, default="N/A")
print(result) # "N/A"
Logging Errors¶
import jsonatapy
import logging
logger = logging.getLogger(__name__)
def evaluate_with_logging(expression, data):
"""Evaluate with error logging."""
try:
return jsonatapy.evaluate(expression, data)
except ValueError as e:
logger.error(f"JSONata error: {e}", extra={
"expression": expression,
"data": data
})
raise
Context Manager Pattern¶
import jsonatapy
from contextlib import contextmanager
@contextmanager
def jsonata_context(expression):
"""Context manager for JSONata expression."""
try:
expr = jsonatapy.compile(expression)
yield expr
except ValueError as e:
print(f"Error: {e}")
yield None
# Usage
with jsonata_context("items[price > 100]") as expr:
if expr:
result = expr.evaluate(data)
Debugging Tips¶
1. Test Expression Syntax First¶
import jsonatapy
# Validate syntax before using
expression = "items[price > 100].name"
try:
expr = jsonatapy.compile(expression)
print("Expression is valid")
except ValueError as e:
print(f"Syntax error: {e}")
2. Use Simple Test Data¶
import jsonatapy
# Test with minimal data
test_data = {"items": [{"price": 150, "name": "Widget"}]}
result = jsonatapy.evaluate("items[price > 100].name", test_data)
print(result) # ["Widget"]
3. Break Down Complex Expressions¶
import jsonatapy
data = {"orders": [{"total": 100}, {"total": 200}]}
# Test each part separately
step1 = jsonatapy.evaluate("orders", data)
print("Step 1:", step1)
step2 = jsonatapy.evaluate("orders.total", data)
print("Step 2:", step2)
step3 = jsonatapy.evaluate("$sum(orders.total)", data)
print("Step 3:", step3)
4. Check Data Types¶
import jsonatapy
# Verify data structure matches expression
data = {"value": "123"} # String, not number
# This will return None (undefined)
result = jsonatapy.evaluate("value > 100", data)
print(result) # None
# Fix: Convert to number
result = jsonatapy.evaluate("$number(value) > 100", data)
print(result) # True
5. Use JSONata Playground¶
Test expressions online first: - Visit try.jsonata.org - Test your expression with sample data - Debug syntax issues before using in Python
6. Add Verbose Error Handling¶
import jsonatapy
import traceback
def debug_evaluate(expression, data):
"""Evaluate with detailed error information."""
print(f"Expression: {expression}")
print(f"Data: {data}")
try:
result = jsonatapy.evaluate(expression, data)
print(f"Result: {result}")
return result
except ValueError as e:
print(f"Error: {e}")
traceback.print_exc()
return None
Error Message Examples¶
Parse Error Messages¶
Unexpected token '[' at position 5
Expected ']' at end of expression
Invalid number format at position 12
Unterminated string literal at position 8
Evaluation Error Messages¶
Unknown function: myFunc
Argument 1 of function "sum" must be an array
Division by zero
Cannot access property "field" of undefined
Stack overflow (recursion depth exceeded)
Type Error Messages¶
Cannot convert "text" to number
Expected array, got string
Cannot apply operator '+' to types object and number
Best Practices¶
1. Fail Fast During Initialization¶
import jsonatapy
class DataTransformer:
def __init__(self, expression):
# Compile at init time to catch syntax errors early
self.expr = jsonatapy.compile(expression)
def transform(self, data):
# Only evaluation errors possible here
return self.expr.evaluate(data)
2. Provide User-Friendly Error Messages¶
import jsonatapy
def user_friendly_evaluate(expression, data):
"""Evaluate with user-friendly error messages."""
try:
return jsonatapy.evaluate(expression, data)
except ValueError as e:
error_msg = str(e)
if "Unknown function" in error_msg:
return {"error": "The function you used doesn't exist. Check the function name."}
elif "Unexpected token" in error_msg:
return {"error": "Your expression has a syntax error. Please check the syntax."}
else:
return {"error": f"An error occurred: {error_msg}"}
3. Handle Errors at the Right Level¶
import jsonatapy
def process_records(records, expression):
"""Process multiple records with per-record error handling."""
expr = jsonatapy.compile(expression) # Fail fast if syntax error
results = []
for i, record in enumerate(records):
try:
result = expr.evaluate(record)
results.append({"success": True, "result": result})
except ValueError as e:
results.append({"success": False, "error": str(e), "record_index": i})
return results
4. Document Expected Errors¶
import jsonatapy
def transform_data(expression: str, data: dict) -> dict:
"""
Transform data using JSONata expression.
Args:
expression: JSONata expression string
data: Input data dictionary
Returns:
Transformed data
Raises:
ValueError: If expression syntax is invalid or evaluation fails
Example:
>>> transform_data("items[price > 100]", {"items": [...]})
"""
return jsonatapy.evaluate(expression, data)