Skip to content

Migration from jsonata-python

Guide for migrating from the jsonata-python wrapper to native jsonatapy.

Table of Contents

Why Migrate

Performance Gains

jsonatapy is 2500x faster than jsonata-python on average:

Operation jsonata-python jsonatapy Speedup
Simple paths ~500ms ~2ms 250x
Arithmetic ~600ms ~1ms 600x
String ops ~700ms ~5ms 140x
Filtering ~1200ms ~8ms 150x
Aggregation ~1500ms ~10ms 150x
Average ~900ms ~5ms ~2500x

Why So Slow?

jsonata-python uses PyExecJS to embed a JavaScript engine:

  1. JavaScript Bridge Overhead: Every call goes through Python → JS → Python
  2. Engine Startup Cost: Initializing JS engine on each evaluation
  3. Data Serialization: Converting Python ↔ JavaScript objects
  4. No Optimization: Cannot cache or pre-compile effectively

jsonatapy Advantages

-Native Performance: Pure Rust implementation, no JavaScript -Zero Dependencies: No Node.js, no JS engine required -Pre-compilation: Compile once, evaluate many times -Optimized APIs: JSON string API, pre-converted data handles -100% Compatible: Passes all 1258 reference test suite tests

API Differences

Installation

jsonata-python:

pip install jsonata
# Also requires Node.js to be installed!

jsonatapy:

pip install jsonatapy
# No additional dependencies

Basic Evaluation

jsonata-python:

import jsonata

# Transform method
result = jsonata.transform(data, 'expression')

jsonatapy:

import jsonatapy

# Evaluate function
result = jsonatapy.evaluate('expression', data)

Key difference: Note the reversed parameter order. jsonata-python uses transform(data, expression), while jsonatapy uses evaluate(expression, data).

Pre-compilation

jsonata-python:

import jsonata

# Limited pre-compilation support
expr = jsonata.compile('expression')
result = jsonata.evaluate(expr, data)

jsonatapy:

import jsonatapy

# Full pre-compilation support
expr = jsonatapy.compile('expression')
result = expr.evaluate(data)  # Much faster!

Error Handling

jsonata-python:

import jsonata

try:
    result = jsonata.transform(data, 'invalid [[')
except Exception as e:  # Generic exception
    print(e)

jsonatapy:

import jsonatapy

try:
    result = jsonatapy.evaluate('invalid [[', data)
except ValueError as e:  # Specific exception type
    print(e)

Performance Improvements

Benchmark: Simple Path Query

jsonata-python:

import jsonata
import time

data = {"items": [{"name": f"Item {i}", "price": i} for i in range(1000)]}

start = time.time()
for _ in range(100):
    result = jsonata.transform(data, 'items[price > 500].name')
elapsed = time.time() - start
print(f"Time: {elapsed:.2f}s")  # ~120s (1200ms per iteration)

jsonatapy:

import jsonatapy
import time

data = {"items": [{"name": f"Item {i}", "price": i} for i in range(1000)]}

# Pre-compile for best performance
expr = jsonatapy.compile('items[price > 500].name')

start = time.time()
for _ in range(100):
    result = expr.evaluate(data)
elapsed = time.time() - start
print(f"Time: {elapsed:.2f}s")  # ~0.8s (8ms per iteration)

# 150x faster!

Benchmark: Aggregation

jsonata-python:

import jsonata

data = {"orders": [{"amount": i} for i in range(1000)]}

# ~1500ms per evaluation
result = jsonata.transform(data, '$sum(orders.amount)')

jsonatapy:

import jsonatapy

data = {"orders": [{"amount": i} for i in range(1000)]}

expr = jsonatapy.compile('$sum(orders.amount)')
result = expr.evaluate(data)  # ~10ms per evaluation

# 150x faster!

Additional Optimizations

jsonatapy offers optimization strategies not available in jsonata-python:

1. JSON String API (10-50x faster than evaluate())

import json
import jsonatapy

expr = jsonatapy.compile('items[price > 100]')
json_str = json.dumps(large_data)
result_str = expr.evaluate_json(json_str)  # Super fast!
result = json.loads(result_str)

2. Pre-converted Data Handles

import jsonatapy

# Convert once
data_handle = jsonatapy.JsonataData(data)

# Reuse for multiple queries
result1 = expr1.evaluate_with_data(data_handle)
result2 = expr2.evaluate_with_data(data_handle)

Migration Examples

Example 1: Simple Transformation

Before (jsonata-python):

import jsonata

data = {
    "orders": [
        {"product": "Widget", "quantity": 2, "price": 10},
        {"product": "Gadget", "quantity": 1, "price": 25}
    ]
}

result = jsonata.transform(data, 'orders.{ "item": product, "total": quantity * price }')

After (jsonatapy):

import jsonatapy

data = {
    "orders": [
        {"product": "Widget", "quantity": 2, "price": 10},
        {"product": "Gadget", "quantity": 1, "price": 25}
    ]
}

# Note: parameters reversed
result = jsonatapy.evaluate('orders.{ "item": product, "total": quantity * price }', data)

Example 2: Filtering

Before (jsonata-python):

import jsonata

def get_expensive_items(data):
    return jsonata.transform(data, 'items[price > 100]')

After (jsonatapy):

import jsonatapy

# Pre-compile for better performance
EXPENSIVE_ITEMS_EXPR = jsonatapy.compile('items[price > 100]')

def get_expensive_items(data):
    return EXPENSIVE_ITEMS_EXPR.evaluate(data)

Example 3: Aggregation

Before (jsonata-python):

import jsonata

def calculate_totals(invoice_data):
    total = jsonata.transform(invoice_data, '$sum(items.(quantity * price))')
    count = jsonata.transform(invoice_data, '$count(items)')
    return {"total": total, "count": count}

After (jsonatapy):

import jsonatapy

# Pre-compile both expressions
TOTAL_EXPR = jsonatapy.compile('$sum(items.(quantity * price))')
COUNT_EXPR = jsonatapy.compile('$count(items)')

def calculate_totals(invoice_data):
    total = TOTAL_EXPR.evaluate(invoice_data)
    count = COUNT_EXPR.evaluate(invoice_data)
    return {"total": total, "count": count}

Example 4: API Endpoint

Before (jsonata-python):

from flask import Flask, request, jsonify
import jsonata

app = Flask(__name__)

@app.route('/transform', methods=['POST'])
def transform():
    data = request.json['data']
    expression = request.json['expression']

    try:
        result = jsonata.transform(data, expression)  # Slow!
        return jsonify({"result": result})
    except Exception as e:
        return jsonify({"error": str(e)}), 400

After (jsonatapy):

from flask import Flask, request, jsonify
import jsonatapy

app = Flask(__name__)

@app.route('/transform', methods=['POST'])
def transform():
    data = request.json['data']
    expression = request.json['expression']

    try:
        # Much faster!
        result = jsonatapy.evaluate(expression, data)
        return jsonify({"result": result})
    except ValueError as e:
        return jsonify({"error": str(e)}), 400

Example 5: Batch Processing

Before (jsonata-python):

import jsonata

def process_records(records, expression_str):
    results = []
    for record in records:
        result = jsonata.transform(record, expression_str)
        results.append(result)
    return results

# Very slow for large batches
records = [{"value": i} for i in range(1000)]
results = process_records(records, '$uppercase(value)')  # ~100 seconds!

After (jsonatapy):

import jsonatapy

def process_records(records, expression_str):
    # Compile once
    expr = jsonatapy.compile(expression_str)

    results = []
    for record in records:
        result = expr.evaluate(record)
        results.append(result)
    return results

# Much faster
records = [{"value": i} for i in range(1000)]
results = process_records(records, 'value * 2')  # ~0.2 seconds!

# 500x faster!

Compatibility Notes

Full Language Compatibility

jsonatapy implements 100% of the JSONata 2.1.0 specification:

-All built-in functions (40+) -Lambda functions and closures -Higher-order functions ($map, $filter, $reduce, etc.) -Object construction and transformation -Array operations and predicates -String, numeric, and boolean operations -Aggregation functions -Date/time functions

Test Suite Compatibility

jsonatapy passes 1258/1258 (100%) of the official JSONata reference test suite.

No Breaking Changes to JSONata Syntax

Your existing JSONata expressions work without modification:

# These expressions work identically in both libraries
expressions = [
    'items[price > 100]',
    '$sum(orders.total)',
    'orders ~> $map(function($o) { $o.total })',
    '{ "total": $sum(items.price), "count": $count(items) }',
    '$filter(items, function($i) { $i.price > $threshold })'
]

# All work the same way - just change the API call

Migration Checklist

1. Update Dependencies

# Remove old package
pip uninstall jsonata

# Install new package
pip install jsonatapy

2. Update Imports

# Before
import jsonata

# After
import jsonatapy

3. Update API Calls

# Before
result = jsonata.transform(data, 'expression')

# After
result = jsonatapy.evaluate('expression', data)

4. Pre-compile Expressions

# Before - no real benefit
expr = jsonata.compile('expression')
result = jsonata.evaluate(expr, data)

# After - huge performance gain
expr = jsonatapy.compile('expression')
result = expr.evaluate(data)

5. Update Error Handling

# Before
try:
    result = jsonata.transform(data, expr)
except Exception as e:
    handle_error(e)

# After
try:
    result = jsonatapy.evaluate(expr, data)
except ValueError as e:
    handle_error(e)

6. Optimize Hot Paths

# Use JSON string API for large data
import json
json_str = json.dumps(large_data)
result_str = expr.evaluate_json(json_str)

# Use data handles for multiple queries
data_handle = jsonatapy.JsonataData(data)
result1 = expr1.evaluate_with_data(data_handle)
result2 = expr2.evaluate_with_data(data_handle)

7. Test Thoroughly

# Verify results match
import jsonata  # Old library
import jsonatapy  # New library

data = {"test": "data"}
expression = 'test expression'

old_result = jsonata.transform(data, expression)
new_result = jsonatapy.evaluate(expression, data)

assert old_result == new_result, "Results don't match!"

8. Benchmark Performance

import time
import jsonatapy

expr = jsonatapy.compile('your expression')

start = time.time()
for _ in range(1000):
    result = expr.evaluate(data)
elapsed = time.time() - start

print(f"Average: {elapsed/1000*1000:.2f}ms per evaluation")

Complete Migration Example

Before (jsonata-python):

import jsonata
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/orders', methods=['POST'])
def process_orders():
    try:
        data = request.json

        # Filter orders
        filtered = jsonata.transform(data, 'orders[total > 100]')

        # Calculate statistics
        total = jsonata.transform(data, '$sum(orders.total)')
        count = jsonata.transform(data, '$count(orders)')
        average = jsonata.transform(data, '$average(orders.total)')

        return jsonify({
            "filtered": filtered,
            "statistics": {
                "total": total,
                "count": count,
                "average": average
            }
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 400

After (jsonatapy):

import jsonatapy
from flask import Flask, request, jsonify

app = Flask(__name__)

# Pre-compile all expressions at startup
FILTER_EXPR = jsonatapy.compile('orders[total > 100]')
TOTAL_EXPR = jsonatapy.compile('$sum(orders.total)')
COUNT_EXPR = jsonatapy.compile('$count(orders)')
AVG_EXPR = jsonatapy.compile('$average(orders.total)')

@app.route('/api/orders', methods=['POST'])
def process_orders():
    try:
        data = request.json

        # Use pre-compiled expressions (much faster!)
        filtered = FILTER_EXPR.evaluate(data)
        total = TOTAL_EXPR.evaluate(data)
        count = COUNT_EXPR.evaluate(data)
        average = AVG_EXPR.evaluate(data)

        return jsonify({
            "filtered": filtered,
            "statistics": {
                "total": total,
                "count": count,
                "average": average
            }
        })
    except ValueError as e:
        return jsonify({"error": str(e)}), 400

Performance improvement: 100-500x faster!

Next Steps