Porting a Language Interpreter in a Day: What the JSONata Rewrite Actually Reveals
Source: simonwillison
There is a category of software that nobody wants to maintain but everybody depends on: the query engine embedded in someone else’s integration platform. JSONata sits squarely in that category. It is the expression language behind IBM App Connect, Node-RED flows, and dozens of enterprise integration tools that process JSON transformations at scale. When a team at what Simon Willison describes as “Vine” wrote up how they ported JSONata with AI in a day and saved half a million dollars a year in the process, it was easy to read it as another “AI did our work” headline. It is more useful to read it as a case study in what AI-assisted migration actually requires to succeed.
What JSONata Is and Why It Has a Porting Problem
JSONata is a declarative query and transformation language for JSON, originally developed by Andrew Coleman at IBM and first released in 2016. Its syntax borrows from XPath and functional programming, and it covers a lot of ground in a compact notation. A simple expression like this:
Account.Order.Product.(Price * Quantity)
Navigates nested JSON, applies arithmetic, and returns an array of computed values across all matching nodes. Add a predicate and you get filtering:
Account.Order.Product[Price > 10].Description
JSONata also ships with roughly 80 built-in functions covering strings, numbers, arrays, objects, date/time, and higher-order operations:
$sum(Account.Order.Product.Price)
$map(Account.Order.Product, function($v, $i) {
$v.Description & " (#" & $string($i) & ")"
})
The reference implementation is JavaScript, published on npm as the jsonata package, and runs to roughly 4,000 lines of code. It implements a Pratt parser (top-down operator precedence) for the expression grammar and a recursive evaluator that handles JSONata’s somewhat unusual semantics around sequences, singletons, and undefined propagation.
Here is the porting problem: if your application is not JavaScript, you have a few bad options. You can shell out to a Node.js subprocess. You can run a small Express server alongside your main process just to evaluate JSONata expressions. You can use a JVM-based interpreter if one exists for your target language. Or you can maintain an FFI boundary between your Go or Rust or Java code and a bundled JavaScript runtime.
None of these are good. Each adds latency, operational complexity, another process to monitor, and in production environments at scale, real infrastructure cost. The Stedi team, which built an EDI and B2B integration platform where JSONata is central to their mapping layer, invested significant engineering time producing jsonata-rs, a Rust port of the evaluator. That work was not trivial. Porting a language runtime is not like porting a utility library.
The Interpreter as a Porting Target
The question worth examining is why a language interpreter is actually a tractable target for AI-assisted migration in a way that most software is not.
Consider what a JSONata interpreter is: a lexer, a Pratt parser that builds an AST, and a recursive evaluator that walks the tree. Each of these is a well-understood structure. The Pratt parsing technique, described by Vaughan Pratt in 1973 and popularized for modern use by Bob Nystrom in Crafting Interpreters, maps predictably from one language to another. The token types, precedence table, and prefix/infix parse functions in JavaScript translate to equivalent constructs in Go or Rust or Python with a high degree of mechanical correspondence.
The evaluator is similarly structured. JSONata’s evaluation logic branches on node type and recurses. There is no magic concurrency, no platform-specific I/O, no dependency on browser APIs or runtime introspection. The semantics are fully specified by the JSONata documentation and, more usefully for porting, by the test suite. The JSONata test suite contains thousands of cases covering expressions, edge cases, error conditions, and all built-in functions.
That test suite is the key. When you port an interpreter manually, you are constantly asking: did I get this edge case right? Does my implementation of $reduce match the reference behavior when the array is empty? Does predicate evaluation on a singleton behave like predicate evaluation on a sequence? With a comprehensive test suite, you have a spec that runs. You write the port, run the tests, and iterate on failures. The correctness criteria are externally defined and mechanically checkable.
AI tools operating in an agentic loop, where they write code, run tests, read failures, and revise, have exactly the kind of feedback signal they need to converge on a correct implementation. This is qualitatively different from asking an AI to port a service that talks to a database, has stateful business logic baked in, and whose correctness is only verifiable by a human reviewing outputs in production.
What the $500K Figure Represents
The $500K/year savings claim is the number that grabs attention, and it is worth being concrete about what that figure likely represents. It is almost certainly not license fees for using JSONata itself, which is open source under the MIT license. The cost structure instead looks like one or more of:
Infrastructure to run a sidecar process. If you are evaluating JSONata at scale in a language other than JavaScript, you are probably running a Node.js service. At high request volumes across many service instances, that is real memory and CPU overhead, multiplied by cloud pricing and redundancy requirements.
Engineering time maintaining the integration. Keeping a sidecar service running, versioned, deployed, and monitored is ongoing work. Every time your main service deploys, the sidecar has to deploy too. That is operational complexity that accumulates across a team.
Latency and reliability costs. A remote call to evaluate a JSONata expression adds round-trip latency. In a synchronous request path, that latency may need to be absorbed by over-provisioning or by accepting worse p99 response times.
When you collapse the JSONata evaluator into a native library in your primary language, all of those costs go away. The $500K number is plausible for a reasonably sized company running JSONata at meaningful volume.
What AI-Assisted Porting Looks Like in Practice
The ability to complete this kind of port in a day, rather than weeks, comes down to a few specific conditions that happened to align for JSONata.
The source codebase is well-organized. The jsonata-js reference implementation is not a tangled mess. The lexer, parser, and evaluator are reasonably separated. There are no surprising global state patterns or closure-heavy architectures that resist mechanical translation.
The target language semantics are close enough. Porting to Go, Rust, or TypeScript is mechanically simpler than porting to a language with radically different evaluation or memory models. Most of JSONata’s internal operations map cleanly to standard data structures and recursive function calls in any contemporary systems or application language.
The test suite provides fast feedback. Agentic coding tools can run go test or cargo test after each iteration, read the failing cases, and know exactly what to fix. This closed loop is what enables the rapid convergence that makes a one-day port believable.
None of this means the resulting code was production-ready on day one. Porting the evaluator to pass the test suite is not the same as handling all the edge cases you will encounter in production JSONata usage, particularly around complex nested predicates, lambda closures, and the more obscure parts of the sequence type system. A day to get a passing test suite is realistic. A day to get something you would bet $500K of infrastructure on requires a longer shake-out period. The honest reading of the claim is probably that the foundational port took a day, not that the entire migration including production validation was complete in that time.
Where This Does Not Generalize
The JSONata case is genuinely encouraging, but it is bounded. It works because the source is a self-contained, pure computation with a comprehensive test suite, no external state dependencies, and well-understood semantics. Most software does not have these properties.
Attempting the same approach on a service with implicit database schema assumptions, accumulated business logic buried in migration scripts, or correctness criteria that exist only in someone’s head will produce a port that looks right until it doesn’t. The AI can mechanically translate the code, but it cannot reconstruct the intent behind a ten-year-old conditional that someone added to handle a specific client’s data format.
The useful generalization from this story is narrower and more actionable: for any well-specified, test-backed library whose primary job is computation over structured data, AI-assisted porting has crossed a threshold where the time cost is genuinely small. Language runtimes, serialization libraries, compression utilities, data structure implementations, cryptographic primitives. If the spec is in the tests and the code does what the tests say, this kind of migration is now a different category of problem than it was two years ago.
That is a meaningful shift in how teams should think about the cost of depending on a library that only exists in one language. The foreign runtime tax is no longer something you accept for the lifetime of your system.