Migration from jsonata-python¶
Guide for migrating from the jsonata-python wrapper to native jsonatapy.
Table of Contents¶
- Why Migrate
- API Differences
- Performance Improvements
- Migration Examples
- Compatibility Notes
- Migration Checklist
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:
- JavaScript Bridge Overhead: Every call goes through Python → JS → Python
- Engine Startup Cost: Initializing JS engine on each evaluation
- Data Serialization: Converting Python ↔ JavaScript objects
- 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:
jsonatapy:
Basic Evaluation¶
jsonata-python:
jsonatapy:
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):
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¶
2. Update Imports¶
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!