jsonatapy is a high-performance Rust implementation of JSONata with Python bindings. This page presents benchmark comparisons against other JSONata implementations.
These numbers come from a dedicated, self-hosted Mac Mini (Apple Silicon), not a shared cloud CI runner — single-tenant physical hardware with no other workloads competing for CPU. This matters: single-sample measurements on a shared/virtualized runner were previously noisy enough that identical code, measured twice, swung -66% to +120%. Every number below is also the minimum of 5 independent measurement trials per test (not an average) — for CPU-bound microbenchmarks, interference can only make a run slower than the code's true achievable speed, never faster, so the minimum across repeated trials is the best available estimate of that true speed.
Implementations Tested
| Implementation |
Language |
Version |
Description |
| jsonatapy |
Rust + Python |
2.2.12 |
This project (compiled Rust extension via PyO3); data crosses the boundary as Python dicts |
| jsonatapy (JSON string I/O) |
Rust + Python |
2.2.12 |
Same library via evaluate_json: data crosses as JSON strings, parsed/serialized by serde per call |
| jsonata-core (pure Rust) |
Rust |
2.2.12 |
This project's engine measured as a Rust library — no Python at all, data pre-parsed, expression pre-compiled (criterion methodology, per table row) |
| jsonata-js |
JavaScript |
2.2.2 |
Reference implementation (Node.js v20.20.2) |
| jsonata-python |
Python |
unknown |
Python wrapper embedding a JS engine (Duktape) |
| jsonata-rs |
Rust |
0.3 |
Third-party Rust implementation (Stedi's crate — not this project; CLI harness, no Python overhead) |
Methodology: compile-once, evaluate-many
Every implementation below is measured the way a real caller who evaluates the same expression repeatedly would use it, not its slowest possible one-off call:
- jsonatapy —
jsonatapy.compile(expr) once, then .evaluate(data) in the timed loop. No further reuse is available; the compiled bytecode is already cached on the expression object.
- jsonata-core (pure Rust) —
Expression::compile(expr) once, input parsed to a JValue once, then Expression::evaluate(&data) in an in-process timed loop with warmup — the same methodology as the criterion suite (benches/), reported per table row. The gap between this column and the jsonatapy columns is the Python boundary cost; the engine is identical.
-
jsonata-js — jsonata(expr) once, then .evaluate(data) in the timed loop. Same story: this is already the library's fastest repeated-call path.
-
jsonata-python — uses its documented Context object (ctx = jsonata.Context(), then ctx(expr, data) in the loop) rather than the one-off transform() convenience function. transform() re-bootstraps an embedded Duktape engine — reloading the jsonata.js library into it — on every single call; reusing a Context keeps that engine warm and is the library's own documented path for repeated evaluation. It is not a true compile-once equivalent, since Context.__call__ still re-parses the expression string on every call, so some of the remaining gap to jsonatapy/jsonata-js is real parsing cost this library doesn't let a caller amortize away.
Benchmarks run on 2026-10-20.
Summary by Category
| Category |
jsonatapy vs JS |
| Simple Paths |
5.6x faster |
| Array Operations |
3.8x faster |
| Complex Transformations |
8.0x faster |
| Deep Nesting |
3.3x faster |
| String Operations |
6.8x faster |
| Higher-Order Functions |
12.3x faster |
| Realistic Workload |
9.7x faster |
Detailed Results
Simple Paths
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| Simple Path |
tiny |
3.404 |
4.530 |
0.649 |
16.950 |
1828.735 |
61.720 |
5.0x faster |
| Deep Path (5 levels) |
tiny |
4.300 |
6.530 |
1.214 |
25.290 |
3007.201 |
72.317 |
5.9x faster |
| Array Index Access |
100 elements |
5.223 |
9.060 |
1.322 |
11.320 |
938.527 |
86.545 |
2.2x faster |
| Arithmetic Expression |
tiny |
2.434 |
3.833 |
0.693 |
22.450 |
2610.019 |
56.884 |
9.2x faster |
Array Operations
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| Array Sum (100 elements) |
100 elements |
1.552 |
2.330 |
0.764 |
6.820 |
304.152 |
17.949 |
4.4x faster |
| Array Max (100 elements) |
100 elements |
1.338 |
2.109 |
0.519 |
6.430 |
297.051 |
17.958 |
4.8x faster |
| Array Count (100 elements) |
100 elements |
2.129 |
3.687 |
0.520 |
8.680 |
528.524 |
34.971 |
4.1x faster |
| Array Sum (1000 elements) |
1000 elements |
2.470 |
3.914 |
1.053 |
4.670 |
179.011 |
24.192 |
1.9x faster |
| Array Max (1000 elements) |
1000 elements |
1.973 |
3.410 |
0.555 |
3.850 |
166.481 |
24.144 |
2.0x faster |
| Array Sum (10000 elements) |
10000 elements |
6.103 |
10.034 |
2.523 |
8.870 |
353.030 |
59.941 |
1.5x faster |
| Array Mapping (extract field) |
100 objects |
7.896 |
26.010 |
1.273 |
21.450 |
2674.303 |
205.974 |
2.7x faster |
| Array Mapping + Sum |
100 objects |
7.555 |
25.315 |
2.013 |
24.770 |
2979.160 |
205.888 |
3.3x faster |
| Array Filtering (predicate) |
100 objects |
5.207 |
16.578 |
1.972 |
50.790 |
6730.847 |
107.011 |
9.8x faster |
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| Object Construction (simple) |
tiny |
3.887 |
3.766 |
2.955 |
26.760 |
2532.480 |
33.965 |
6.9x faster |
| Object Construction (nested) |
tiny |
6.077 |
4.964 |
4.278 |
33.410 |
2898.102 |
36.880 |
5.5x faster |
| Conditional Expression |
tiny |
1.118 |
1.660 |
0.393 |
13.630 |
1356.527 |
26.342 |
12.2x faster |
| Multiple Nested Functions |
tiny |
2.579 |
2.946 |
2.717 |
19.180 |
1777.014 |
27.906 |
7.4x faster |
Deep Nesting
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| Deep Path (12 levels) |
12 levels |
4.510 |
6.165 |
0.993 |
25.880 |
2912.411 |
55.386 |
5.7x faster |
| Nested Array Access |
4-level nested arrays |
8.435 |
13.128 |
0.599 |
7.980 |
611.698 |
107.419 |
1.1x slower |
String Operations
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| String Uppercase |
tiny |
3.946 |
4.569 |
3.147 |
22.560 |
2395.996 |
53.487 |
5.7x faster |
| String Lowercase |
tiny |
3.962 |
4.566 |
3.197 |
22.450 |
2397.158 |
53.252 |
5.7x faster |
| String Length |
tiny |
3.634 |
4.386 |
2.660 |
24.600 |
2597.104 |
54.863 |
6.8x faster |
| String Concatenation |
tiny |
3.063 |
3.147 |
2.786 |
25.230 |
1923.759 |
30.246 |
8.2x faster |
| String Substring |
tiny |
2.843 |
3.197 |
2.909 |
19.460 |
1684.111 |
28.590 |
6.8x faster |
| String Contains |
tiny |
2.097 |
2.534 |
1.649 |
16.170 |
1416.831 |
28.300 |
7.7x faster |
Higher-Order Functions
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| $map with lambda |
100 elements |
1.600 |
1.706 |
1.509 |
23.930 |
2673.195 |
7.207 |
15.0x faster |
| $filter with lambda |
100 elements |
1.639 |
1.763 |
1.415 |
23.490 |
2678.940 |
7.097 |
14.3x faster |
| $reduce with lambda |
100 elements |
3.030 |
3.202 |
2.994 |
22.710 |
2690.943 |
7.747 |
7.5x faster |
Realistic Workload
| Operation |
Data Size |
jsonatapy |
jsonatapy (json I/O) |
jsonata-core (pure Rust) |
jsonata-js |
jsonata-python |
jsonata-rs |
vs JS |
| Filter by category |
100 products |
7.512 |
41.332 |
2.089 |
52.020 |
7431.301 |
313.541 |
6.9x faster |
| Calculate total value |
100 products |
7.763 |
40.220 |
4.197 |
37.620 |
5155.273 |
313.388 |
4.8x faster |
| Complex transformation |
100 products |
14.476 |
23.511 |
8.471 |
87.790 |
9303.218 |
135.284 |
6.1x faster |
| Group by category (aggregate) |
100 products |
11.520 |
23.559 |
7.974 |
88.190 |
N/A |
133.484 |
7.7x faster |
| Top rated products |
100 products |
1.645 |
9.070 |
1.037 |
37.460 |
4512.405 |
66.834 |
22.8x faster |
Path Comparison
| Operation |
jsonatapy (ms) |
Iterations |
| Filter by category (data handle) |
11.766 |
500 |
| Filter by category (data→json) |
5.640 |
500 |
| Complex transformation (data handle) |
27.195 |
500 |
| Complex transformation (data→json) |
23.194 |
500 |
| Aggregate (data handle) |
4.726 |
500 |
| Aggregate (data→json) |
4.670 |
500 |
Faster than JavaScript:
- Simple Paths (5.6x faster)
- Array Operations (3.8x faster)
- Complex Transformations (8.0x faster)
- Deep Nesting (3.3x faster)
- String Operations (6.8x faster)
- Higher-Order Functions (12.3x faster)
- Realistic Workload (9.7x faster)
Comparable to JavaScript:
Optimizing Array Workloads
For array-heavy workloads, the dominant cost is converting Python dicts to Rust values on every call. Use JsonataData to pre-convert data once and reuse across multiple evaluations:
import jsonatapy
data = {...} # your data
expr = jsonatapy.compile("products[price > 100]")
# Pre-convert once
jdata = jsonatapy.JsonataData(data)
# Reuse many times (3-15x faster than evaluate(dict))
result = expr.evaluate_with_data(jdata)
Methodology
- Date: 2026-10-20
- Platform: GitHub Actions (self-hosted Michaels-Mini, physical/dedicated hardware, macOS ARM64)
- Python: 3.14.6
- Node.js: v20.20.2
- All times are total wall-clock time for the stated number of iterations
- Each benchmark includes a warmup phase before measurement
- 'vs JS' column shows jsonatapy speedup relative to the JavaScript reference implementation
- Values > 1x mean jsonatapy is faster; < 1x means JavaScript is faster