Skip to content

Usage Guide

Common patterns and examples for jsonatapy.

Basic Queries

Simple Path Access

import jsonatapy

data = {
    "user": {
        "name": "Alice",
        "email": "alice@example.com"
    }
}

# Single field
result = jsonatapy.evaluate("user.name", data)
# "Alice"

# Nested field
result = jsonatapy.evaluate("user.email", data)
# "alice@example.com"

Array Access

data = {
    "items": ["apple", "banana", "orange"]
}

# Single element
result = jsonatapy.evaluate("items[0]", data)
# "apple"

# Array slicing
result = jsonatapy.evaluate("items[1..2]", data)
# ["banana", "orange"]

Filtering and Mapping

Array Filtering

data = {
    "products": [
        {"name": "Laptop", "price": 1200},
        {"name": "Mouse", "price": 25},
        {"name": "Keyboard", "price": 75}
    ]
}

# Filter by condition
result = jsonatapy.evaluate("products[price > 50]", data)
# [{"name": "Laptop", "price": 1200}, {"name": "Keyboard", "price": 75}]

# Extract specific field
result = jsonatapy.evaluate("products[price > 50].name", data)
# ["Laptop", "Keyboard"]

Array Mapping

# Transform array elements
result = jsonatapy.evaluate(
    'products.{"item": name, "cost": price}',
    data
)
# [{"item": "Laptop", "cost": 1200}, ...]

Aggregation

data = {
    "orders": [
        {"quantity": 2, "price": 10},
        {"quantity": 3, "price": 15},
        {"quantity": 1, "price": 20}
    ]
}

# Sum
total = jsonatapy.evaluate("$sum(orders.(quantity * price))", data)
# 85

# Count
count = jsonatapy.evaluate("$count(orders)", data)
# 3

# Average
avg = jsonatapy.evaluate("$average(orders.price)", data)
# 15

# Min/Max
min_price = jsonatapy.evaluate("$min(orders.price)", data)
max_price = jsonatapy.evaluate("$max(orders.price)", data)

String Operations

data = {"name": "alice"}

# Uppercase
result = jsonatapy.evaluate("$uppercase(name)", data)
# "ALICE"

# Lowercase
result = jsonatapy.evaluate("$lowercase(name)", data)
# "alice"

# Concatenation
result = jsonatapy.evaluate('"Hello, " & name', data)
# "Hello, alice"

# Substring
result = jsonatapy.evaluate('$substring("hello", 1, 4)', {})
# "ell"

# Contains
result = jsonatapy.evaluate('$contains("hello", "ell")', {})
# true

Conditional Expressions

data = {"price": 150}

# Ternary operator
result = jsonatapy.evaluate(
    'price > 100 ? "expensive" : "affordable"',
    data
)
# "expensive"

# Conditional field
result = jsonatapy.evaluate(
    '{"price": price, "category": price > 100 ? "premium" : "standard"}',
    data
)
# {"price": 150, "category": "premium"}

Object Construction

data = {
    "firstName": "Alice",
    "lastName": "Smith",
    "age": 30
}

# Build new object
result = jsonatapy.evaluate(
    '{"fullName": firstName & " " & lastName, "age": age}',
    data
)
# {"fullName": "Alice Smith", "age": 30}

Using Bindings

# Define variables
expr = jsonatapy.compile("items[price > $threshold].name")

result = expr.evaluate(
    {"items": [{"name": "A", "price": 100}, {"name": "B", "price": 50}]},
    {"threshold": 75}
)
# ["A"]

Compiled Expressions

For repeated evaluations, compile once:

expr = jsonatapy.compile("products[category=$cat].name")

# Evaluate with different data
data1 = {
    "products": [
        {"name": "Item1", "category": "electronics"},
        {"name": "Item2", "category": "books"}
    ]
}

result1 = expr.evaluate(data1, {"cat": "electronics"})
# ["Item1"]

result2 = expr.evaluate(data1, {"cat": "books"})
# ["Item2"]

Higher-Order Functions

Map

data = {"numbers": [1, 2, 3, 4, 5]}

result = jsonatapy.evaluate(
    "$map(numbers, function($n) { $n * 2 })",
    data
)
# [2, 4, 6, 8, 10]

Filter

result = jsonatapy.evaluate(
    "$filter(numbers, function($n) { $n > 3 })",
    data
)
# [4, 5]

Reduce

result = jsonatapy.evaluate(
    "$reduce(numbers, function($acc, $n) { $acc + $n }, 0)",
    data
)
# 15

Error Handling

def safe_evaluate(expression, data):
    try:
        return jsonatapy.evaluate(expression, data)
    except ValueError as e:
        print(f"Error: {e}")
        return None

result = safe_evaluate("invalid[[syntax", {})
# Prints: Error: Parse error...
# Returns: None

Guardrails

jsonatapy accepts three optional keyword arguments, on compile()/JsonataExpression.compile() (as defaults) and on every evaluate*() call (as a per-call override), to protect against runaway or adversarial expressions. All three default to None (unlimited), matching prior behavior exactly when unspecified.

  • timeout — maximum evaluation time in milliseconds. Raises ValueError with a D1012 code.
  • max_stack_depth — maximum recursion depth (e.g. deeply recursive lambdas). Raises ValueError with a D1011 code.
  • max_sequence_length — maximum length of a query-result sequence ($map/$filter/wildcards/ descendants/etc). Raises ValueError with a D2015 code.
import jsonatapy

# Set defaults at compile time...
expr = jsonatapy.compile("$sum(items.price)", timeout=1000, max_sequence_length=100_000)

# ...or override per call
result = expr.evaluate(data, timeout=5000)

# A non-terminating expression is stopped instead of hanging forever
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

See Error Handling for the full list of guardrail error codes.

Performance Optimization

Lazy Conversion by Default (2.2.4+)

evaluate(dict) converts Python data lazily: only the fields your expression actually touches are converted, so simple filters and aggregations over large arrays are several times faster with no code changes. JsonataData remains the fastest option when you evaluate the same data repeatedly — its conversion happens once and is reused across every query, while a plain evaluate(dict) call still converts touched fields on each call.

Result aliasing: parts of a result that come from untouched input subtrees now reference the original Python objects (matches jsonata-js) rather than copies — mutating the result can mutate the input. Use copy.deepcopy(result) first if you need an independent copy.

Compile Once

# Slow - compiles every time
for data in dataset:
    result = jsonatapy.evaluate("items[price > 100]", data)

# Fast - compile once
expr = jsonatapy.compile("items[price > 100]")
for data in dataset:
    result = expr.evaluate(data)

JSON String API

For large datasets:

import json

expr = jsonatapy.compile("items[price > 100]")

# Large data
data = {"items": [...]}  # 1000+ items

# Fast path
json_str = json.dumps(data)
result_str = expr.evaluate_json(json_str)
result = json.loads(result_str)

Real-World Examples

API Response Transformation

api_response = {
    "data": {
        "user": {
            "id": 123,
            "firstName": "Alice",
            "lastName": "Smith",
            "orders": [
                {"id": 1, "total": 100},
                {"id": 2, "total": 200}
            ]
        }
    }
}

expr = jsonatapy.compile('''
{
    "userId": data.user.id,
    "fullName": data.user.firstName & " " & data.user.lastName,
    "totalSpent": $sum(data.user.orders.total)
}
''')

result = expr.evaluate(api_response)
# {"userId": 123, "fullName": "Alice Smith", "totalSpent": 300}

Data Filtering and Grouping

transactions = {
    "transactions": [
        {"region": "North", "amount": 100},
        {"region": "South", "amount": 150},
        {"region": "North", "amount": 200}
    ]
}

# Sum by region
result = jsonatapy.evaluate(
    "$sum(transactions[region='North'].amount)",
    transactions
)
# 300

ETL Pipeline

raw_data = {
    "records": [
        {"name": "alice", "status": "active", "amount": 100},
        {"name": "bob", "status": "inactive", "amount": 200},
        {"name": "charlie", "status": "active", "amount": 150}
    ]
}

transform = jsonatapy.compile('''
records[status="active"].{
    "name": $uppercase(name),
    "value": amount * 1.1
}
''')

result = transform.evaluate(raw_data)
# [{"name": "ALICE", "value": 110}, {"name": "CHARLIE", "value": 165}]