Why JSONata Resisted Correct Ports for Eight Years, and What the AI Rewrite Actually Required
Source: simonwillison
The headline from Reco.ai’s engineering blog is the kind that usually signals oversimplification: we rewrote a library with AI in a day and saved half a million dollars a year. The number is real, the timeline is real, and the technical depth behind both is worth unpacking rather than dismissing.
The subject of the port is JSONata, a query and transformation language for JSON data created by Andrew Coleman at IBM and open-sourced around 2015. If you have not encountered it directly, you have almost certainly used something that runs on it. IBM App Connect uses it as its primary expression language for data mapping. Node-RED embeds it for flow transformations. AWS Step Functions added native JSONata support in late 2024 as an alternative to its cumbersome intrinsic function syntax. The reference implementation is a JavaScript npm package that sees tens of millions of weekly downloads, most of them transitive.
What JSONata Actually Does
JSONata sits in an awkward but genuinely useful position in the JSON tooling ecosystem. JMESPath covers path selection but has no arithmetic, no user-defined functions, and no aggregation. jq is powerful but designed as a command-line tool with a fragile embedding story. JSONPath, formalized as RFC 9535 in 2024, handles navigation and filtering but not transformation. JSONata fills the space between those tools.
The language handles expressions like:
Account.Order.Product.(Price * Quantity)
which navigates a nested structure, performs inline computation per matched element, and returns a sequence of results. Aggregation sits on top of that:
$sum(Account.Order.Product.(Price * Quantity))
The full language includes predicate filters, wildcards, recursive descent, higher-order functions like $map and $reduce, lambda expressions with closures, a pipe operator for point-free composition, and a built-in library of over 60 functions covering strings, numbers, date/time, and aggregation. The formal specification is reasonably complete, though edge case behavior is often encoded in tests rather than prose.
The reference implementation is a tree-walking interpreter: a tokenizer, a Pratt parser that produces an AST, and a recursive evaluator that walks the AST against the input JSON document. No bytecode compilation, no JIT, no external dependencies. The core evaluator is around 3,500 lines of JavaScript.
The Cost Problem
Running JSONata in production at scale from a non-JavaScript environment creates compounding costs. The common approach is a Node.js sidecar: a separate process running the reference implementation, called via IPC or HTTP from your main service. This introduces cold starts in the 200-400ms range, baseline memory overhead around 50MB per instance, per-call latency from the process boundary, and an entire separate deployment and observability surface.
At millions of evaluations per day, that overhead compounds continuously. Cutting evaluation time from 50ms to 5ms per document, across that volume, produces exactly the magnitude of savings Reco.ai reported. $500,000 per year is roughly $58 per hour in continuous compute, which is a plausible number for a high-throughput document transformation pipeline that eliminates a Node.js sidecar in favor of native execution.
The business case for a native port in the target runtime was clear. The engineering investment was not.
Three Semantic Traps That Killed Prior Ports
Community ports of JSONata have existed since roughly 2018. IBM maintains JSONata4Java, started that year and still incomplete as of 2026. Multiple Go and Python attempts exist in similar states. None of them fully pass the conformance test suite. The reason is not algorithmic complexity. A Pratt parser and a tree-walking evaluator are well-understood patterns with extensive reference material. The reason is three layers of semantic behavior that look straightforward and are not.
Sequences are not arrays. JSONata inherits this from XPath. A path expression that matches a single value returns that value directly, not a one-element array. A path that matches multiple values returns a sequence, which flattens automatically in some contexts and behaves differently from an array in others. The [] operator explicitly coerces a sequence into an array. The reference implementation represents sequences as JavaScript arrays with additional metadata. Every prior port that treated sequences as plain arrays passed around 90% of tests and failed at composition boundaries where singleton propagation and flattening semantics interact.
Undefined propagates silently. Navigating a missing path in JSONata returns nothing, not null and not an error. $uppercase(foo.bar.baz) on a document without that path returns nothing rather than throwing. Every operator and all 60+ built-in functions must respect this. In Go, natural nil handling produces wrong output on expressions that rely on silent short-circuiting of undefined subexpressions. In Rust, Option<T> propagation with ? returns early in ways that alter control flow. The surface area is wide: every binary operator, every unary operator, all 60 functions.
The regex engine is JavaScript’s regex engine. $match(), $replace(), and $split() use JavaScript regex semantics, including lookahead, lookbehind, named capture groups, and Unicode property escapes. Go’s regexp package uses RE2 semantics and deliberately excludes lookahead and lookbehind. Rust’s regex crate does the same. A port in either language must either bind PCRE via FFI, document and surface an incompatibility, or implement a JavaScript-compatible regex evaluator. None of these are simple choices, and prior ports made them inconsistently.
These three traps compound. Sequence semantics and undefined propagation interact in predicate expressions. Both interact with how function arguments evaluate when their inputs are missing or singleton. A port that handles each in isolation will still produce wrong output when they combine.
Why the Test Suite Changed the Calculus
The JSONata conformance test suite is a collection of several hundred self-contained JSON test cases, each with an expression, an input document, and an expected output. It is organized by feature group and designed to run against any implementation, not just the reference. A representative test case:
{
"expression": "$sum(Account.Order.Product.(Price * Quantity))",
"data": {
"Account": {
"Order": [
{ "Product": [{ "Price": 10, "Quantity": 3 }] },
{ "Product": [{ "Price": 5, "Quantity": 10 }] }
]
}
},
"result": 80
}
This infrastructure is what makes the AI-assisted port tractable in a way a human port was not. The feedback loop becomes mechanical: translate a module, run the tests, feed failing cases back to the model alongside the relevant translated code and the expected output, iterate. The model does not need to understand the full semantic theory of JSONata to fix a specific failing test. It needs to look at what went wrong in one case and correct the translated code.
The Simon Willison writeup points at this as the critical condition: the test suite is what turns an LLM from a code generator into a code translator with automated acceptance criteria. The documented pass rate progression is roughly: lexer and parser translation gets you to 35%; evaluator work moves that to 68%; the built-in function library brings it to 91%; sequence semantics and context edge cases close most of the remaining gap to around 98%. The last two percent involves JavaScript-specific Unicode normalization and numeric precision decisions that you either resolve or document.
LLMs handle the built-in function library translation particularly well. Sixty functions, each with specific semantics, each requiring consistent handling of undefined propagation, is exactly the kind of repetitive well-specified work where a model stays consistent across all 60 where a human engineer drifts. The model applies the same undefined-propagation pattern to function 50 as to function 1, without the attention loss that makes a manual port accumulate subtle inconsistencies.
What Generalizes and What Does Not
The conditions that made this tractable are specific. JSONata has a formal specification. It has a conformance test suite that serves as an automated acceptance criterion. The source is self-contained JavaScript with no external runtime dependencies, fitting comfortably in a large model’s context. The algorithms involved (Pratt parser, tree-walking evaluator) appear extensively in model training data. The task is translation of a proven architecture, not design of a new one.
This pattern applies to a real category of components: expression evaluators, query engines, protocol parsers, serialization codecs, template renderers, data validation runtimes. The Ajv JSON Schema validator is an example of a JavaScript-originated library with conformance infrastructure that would meet these conditions. The WebAssembly spec tests and ECMAScript Test262 represent the same infrastructure pattern at larger scale.
It does not apply to large systems with implicit environmental dependencies, to components whose behavior is defined by production traffic rather than tests, or to fast-moving upstreams where a fork’s maintenance cost erodes the savings. A native port is a fork. When JSONata’s spec changes or fixes a bug upstream, the ported version must track that manually. That cost needs to be accounted for before the savings look as clean as the headline suggests.
The practical shift is economic. The engineering investment required to port a well-specified library with good test coverage has dropped by roughly an order of magnitude. A project that previously required months of careful work to achieve production-quality spec coverage now requires a day of translation and test-driven iteration. At $50,000 per year in operational overhead, that changes the build-versus-tolerate calculation significantly. At $500,000, it was never seriously in question. The AI assistance changes the threshold at which native implementation becomes worth attempting, and that shift applies across a broader category of libraries than most teams have considered.