Introduction
Jetro is a JSON processor which provides query, transform, and patching, written in Rust. It parses a small dot-syntax DSL, plans the query through a multi-tier optimizer, and routes each subtree to whichever execution backend will run it fastest: zero-copy borrowed views over a simd-json tape, a bitmap structural index, a streaming pull pipeline, or the universal interpreted fallback.
Jetro's shape is deliberately different from a small jq clone. Method chains
compose with lambdas, pattern matching, f-strings, reducers, and document
updates inside one expression language. It also has distinctive features such
as demand propagation, more often associated with lazy languages such as
Haskell, so sinks like first, last, and take(n) can change how much
upstream work is performed. For mutation-heavy workflows,
update can batch compatible path rewrites into one document patch instead of
forcing callers to round-trip through host-language object editing.
jetrocli -e '$.services.filter(@.enabled).map({name: @.name, p95: @.latency_ms})' < services.json
[
{"name":"api","p95":42},
{"name":"worker","p95":85}
]
If you have used jq, Jetro will feel familiar but takes a different shape: it
is method-chain oriented, closer to the collection APIs most application
developers already use.
$.services
.filter(@.enabled)
.sort_by(-latency_ms)
.take(1)
.map({service: name, alert: errors > 5})
That query reads like the code you would otherwise write by hand: keep enabled services, sort by latency, keep the slowest one, return only the fields the next system needs.
Why Developers Reach For Jetro
Use Jetro when the shape of the data matters more than the ceremony around it:
- Inspect production JSON without writing a script. Pull out the one field, row, group, or summary you need from a real payload.
- Embed dynamic transformations. Let users, pipelines, or config files define data-shaping rules without recompiling your service.
- Normalize API and event payloads. Filter, project, rename, aggregate, and label JSON before it crosses a boundary.
- Patch documents deliberately. Use update expressions for migrations, fixture generation, and config rewrites.
- Process NDJSON files from the terminal. Run row-local expressions over
logs and event streams with
jetrocli --ndjson.
What Makes Jetro Different
Jetro is small at the surface, but it is not a toy interpreter.
- The syntax is expression-first. Objects, arrays, lambdas, filters, reducers, string formatting, pattern matching, and updates compose inside one expression language.
- The planner tries to do less work. Queries like
first,take, and bounded projections can tell earlier stages how much data is actually needed. - Writes are part of the language. Updating JSON is not bolted on as a separate API; document rewrites are planned alongside reads.
- There is a real Rust API.
Jetrois the byte-oriented document handle.JetroEngineis the long-lived engine for reusable plans, VM state, and streaming workflows.
A Small Taste
Here is a document shaped like the kind of service inventory developers often meet in scripts, dashboards, and deploy tooling:
{
"services": [
{"name":"api","lang":"rust","latency_ms":42,"owner":"platform","enabled":true,"errors":2},
{"name":"worker","lang":"go","latency_ms":85,"owner":"data","enabled":true,"errors":9},
{"name":"admin","lang":"ts","latency_ms":130,"owner":"platform","enabled":false,"errors":0}
],
"deploys": [
{"service":"api","sha":"a1","status":"ok"},
{"service":"worker","sha":"b2","status":"fail"}
],
"meta": {"env":"prod","version":7}
}
Project the active services:
$.services.filter(@.enabled).map({name: @.name, p95: @.latency_ms, owner: @.owner})
Count ownership:
$.services.count_by(@.owner)
Turn deploy states into operator messages:
$.deploys.map(d => match d with {
{status:"fail",service:s} -> f"rollback {s}",
{status:"ok",service:s} -> f"ship {s}",
_ -> "inspect"
})
Patch the document:
$.update({"meta.version": @ + 1, "services[*].checked": true})
The rest of this book teaches the language from that practical angle: how to read JSON, reshape it, aggregate it, update it, and embed the same behavior in Rust.
Example Conventions
Examples use this layout:
DOC: {"services": [{"name": "api", "enabled": true}, {"name": "admin", "enabled": false}]}
QUERY: $.services.filter(@.enabled).map(@.name)
OUT: ["api"]
Where the input document matters, examples include DOC:. Where the source is
already clear from the section, examples usually show only QUERY: and OUT:.
Method aliases are listed inline, for example unique (alias distinct).
Start with the Quick Tour, then use the Builtin Reference when you need exact method behavior.