Porting a Query Language Evaluator in a Day: What the JSONata Rewrite Actually Shows About AI-Assisted Migration
Source: simonwillison
Simon Willison covered a post from the Vine team about porting the JSONata query language evaluator to a new runtime using AI, completing it in roughly a day and eliminating around $500,000 per year in costs. The headline number is striking, but the more interesting part is why this particular task, rewriting a query language implementation, sits in a category where AI-assisted migration works better than most.
What JSONata Is and Why It Matters
JSONata is a query and transformation language for JSON data, created by Andrew Coleman at IBM. If you’ve worked with Node-RED, IBM App Connect, or any number of integration platforms, you’ve probably used it without thinking about the runtime underneath. The syntax borrows from XPath, allowing you to navigate nested JSON structures, apply predicates, aggregate arrays, and compose transformation pipelines in a compact expression language.
A simple example: given a JSON payload with an array of orders, you can write orders[status='open'].total to extract the totals of open orders, or $sum(orders[status='open'].total) to aggregate them. The language handles path navigation, wildcards, array flattening, conditionals, lambdas, and a substantial standard library covering strings, numbers, arrays, objects, and date/time operations.
The reference implementation is a JavaScript library published on npm. It contains a hand-written parser that produces an AST, followed by an evaluator that walks that tree. The evaluator supports both synchronous and asynchronous operation, with the async path using continuation-passing style to allow custom async binding functions. This is not a small or simple codebase. The parser alone handles operator precedence, string escaping, numeric literals in multiple formats, and a function call syntax that departs from conventional languages in several ways.
The Sidecar Problem
The $500K/year figure makes more sense once you understand the deployment context. JSONata’s reference implementation is JavaScript. If your platform is written in Go, Rust, Python, Java, or essentially anything else, you have a problem: you need to evaluate JSONata expressions somewhere, and the only complete, production-tested implementation is a Node.js library.
The common solution is to run a sidecar service. Your main application makes HTTP or RPC calls to a small Node.js process that handles JSONata evaluation. This pattern works, but it has real costs. You pay for the compute running that sidecar across every environment, every region, every customer deployment. You add a network hop to every transformation. You introduce a failure mode: your data pipeline now has an operational dependency on a separate process. You pay engineering time to maintain the deployment, the health checks, the scaling configuration, and the API contract between your main application and the sidecar.
At scale, that adds up to exactly the kind of number the Vine team cited. The Node.js sidecar is not a theoretical architecture tax; it is a real infrastructure cost that teams running JSONata at volume have been paying for years.
Why This Migration Category Suits AI Well
Code migration tasks vary enormously in how well AI handles them. Rewriting business logic that accumulated over a decade, with implicit requirements scattered across undocumented edge cases and tribal knowledge, is hard. The AI has no way to know what the correct behavior should be in cases the existing code handles silently.
Query language evaluators are different for a specific structural reason: they have exhaustive test suites with well-defined correct answers.
The JSONata project maintains a comprehensive test suite with hundreds of cases covering every operator, every built-in function, and a large number of edge cases around type coercion, error handling, and numeric formatting. These tests define the ground truth. When you port the evaluator to a new language, you do not need to guess what the correct behavior is for a given input; you run the tests and find out.
This changes the migration loop significantly. With AI generating the initial port and the test suite providing immediate, objective feedback, the developer’s job becomes triaging failures and providing corrections, rather than comprehensively reviewing every code path for correctness. The AI can handle the mechanical translation of the parser, the AST node types, and the evaluator functions. The developer focuses on the places where language semantics diverge in ways that matter: number precision, Unicode handling, error propagation, the async evaluation model.
There are also structural properties of the source code that help. JSONata’s evaluator is largely a large switch or dispatch over AST node types, with each case handling one language construct. This structure maps cleanly between languages. The built-in functions are isolated, individually testable, and mostly stateless. A large class switch translating from JavaScript to Go or Rust is exactly the kind of task where current models perform reliably, because the transformation is systematic and the test cases catch deviations immediately.
The Parts That Stay Hard
None of this means the port is free of difficulty. A few areas remain genuinely hard even with AI assistance.
JSONata’s numeric model follows specific rules about when numbers are integers versus decimals in output, and how to format them. JavaScript’s number type silently handles many cases that require explicit attention in typed languages. A Go port needs to decide whether to use float64, *big.Rat, or a decimal library, and that choice affects precision in edge cases that the test suite may not fully exercise.
The async evaluation path is substantially more complex than the sync path. JSONata allows you to register binding functions that return promises, and the evaluator needs to propagate that asynchrony through the tree. In JavaScript, this falls out naturally with async/await. In other languages, the equivalent requires explicit design: coroutines, callbacks, or a different approach to the evaluation loop entirely. AI can propose a translation, but the design decision belongs to the engineer.
Error messages are another subtle area. JSONata produces specific error codes and messages that downstream tooling often parses or displays. A port that changes error text breaks consumers even when the evaluation behavior is correct. This requires going through the error cases systematically, which the test suite partially covers but may not exhaustively address.
The Broader Pattern
The Vine story is part of a pattern that has become more common over the last year or two. Teams running polyglot architectures with a critical dependency on a language-specific library are finding that AI-assisted porting is viable where it previously was not. The combination of capable code generation models, comprehensive test suites, and tight iteration loops has made migrations feasible in days that would previously have taken months and carried significant risk.
What makes this category particularly interesting is that the value is not just about development speed. The artifact produced by the migration, a native implementation without a runtime dependency, is categorically better than the sidecar it replaces. It has lower latency, fewer failure modes, simpler deployment, and no ongoing infrastructure cost. The AI-assisted migration does not just deliver the same solution faster; it enables a solution that was impractical to pursue at all given previous tooling constraints.
For teams in similar situations, the JSONata case is a useful data point. If you have a language-specific library dependency with a good test suite and clear semantics, the migration path through AI assistance is more tractable than it looks. The test suite is doing most of the heavy lifting; the AI handles the translation volume; the engineer handles the semantic edge cases. That division of labor is, at least for well-defined computational libraries, genuinely productive.
The $500K is real, but the more durable insight is the one about test suites: they are not just a quality tool, they are a migration enabler. A library with comprehensive tests can be ported with confidence in a way that undocumented legacy code cannot. If anything, this is an argument for writing better tests now, not because you plan to rewrite your library, but because future-you, or future-AI, will need that specification to work from.