A 5-Minute Tour

This page is a working tour of jetro. Every example has a document, a query, and an output. Run them in your shell with jetrocli, in Rust with Jetro::collect, or in Python with jetro.collect.

The document for this tour

{
  "books": [
    {"title": "Dune", "year": 1965, "author": "Herbert", "tags": ["sf"]},
    {"title": "Foundation", "year": 1951, "author": "Asimov", "tags": ["sf", "hugo"]},
    {"title": "Hyperion", "year": 1989, "author": "Simmons", "tags": ["sf", "hugo"]},
    {"title": "Snow Crash", "year": 1992, "author": "Stephenson", "tags": ["sf", "cyberpunk"]}
  ],
  "active": true
}

1. Path navigation

QUERY:  $.books[0].title
OUT:    "Dune"

$ is the root, .books is field access, [0] is index. Negative indices work: [-1] is "Snow Crash".

2. The whole array

QUERY:  $.books[*].title
OUT:    ["Dune","Foundation","Hyperion","Snow Crash"]

[*] produces every element.

3. Filter

QUERY:  $.books.filter(@.year > 1980).map(@.title)
OUT:    ["Hyperion","Snow Crash"]

Inside .filter, .map, and similar method args, the current item is @. Use @.field to walk into it; the leading-dot shorthand .field is also accepted and desugars to @.field.

4. Four lambda forms

These are all equivalent:

$.books.filter(@.year > 1980)
$.books.filter(.year > 1980)
$.books.filter(b => b.year > 1980)
$.books.filter(lambda b: b.year > 1980)

Pick whichever reads best. The named-lambda and @-forms compile to identical bytecode; benchmarks confirm them perf-equal.

5. Reducers

QUERY:  $.books.count()
OUT:    4

QUERY:  $.books.map(@.year).min()
OUT:    1951

QUERY:  $.books.map(@.year).avg()
OUT:    1724.25

Reducers terminate the streaming pipeline.

6. Group / count / sort

QUERY:  $.books.count_by(@.author)
OUT:    {"Herbert":1,"Asimov":1,"Simmons":1,"Stephenson":1}

QUERY:  $.books.sort(@.year).map(@.title)
OUT:    ["Foundation","Dune","Hyperion","Snow Crash"]

7. Object projection

QUERY:  $.books[0].pick(title, author)
OUT:    {"title":"Dune","author":"Herbert"}

QUERY:  $.books.map(b => b.pick(title, year))
OUT:    [{"title":"Dune","year":1965}, ...]

.pick(name, alias: src) also renames: .pick(t: title, y: year).

QUERY:  $..find(@.year < 1960)
OUT:    [{"title":"Foundation","year":1951,...}]

QUERY:  $..like({author: "Asimov"})
OUT:    [{"title":"Foundation","year":1951,...}]

..find, ..shape, and ..like are DFS pre-order over the whole document. Equivalent named forms: .deep_find, .deep_shape, .deep_like.

9. Pipe and ternary

QUERY:  $.books.count() | "found " + (@ as string) + " books"
OUT:    "found 4 books"

QUERY:  $.books[0] | "old" if @.year < 1980 else "modern"
OUT:    "old"

| passes a value through an expression — not a method-call sugar. Use .method() for methods.

10. F-strings

QUERY:  $.books.map(b => f"{b.title} ({b.year})")
OUT:    ["Dune (1965)","Foundation (1951)","Hyperion (1989)","Snow Crash (1992)"]

11. Pattern match

QUERY:
  match $.books[0] with {
    {year: y} when y < 1970 -> f"classic {y}",
    {year: y} -> f"modern {y}",
    _ -> "unknown"
  }
OUT:    "classic 1965"

Patterns include literals, ranges (1900..2000), or-patterns, guards, object shape, array shape, and rest captures.

12. Writes

QUERY:  $.books[0].year.set(1900)
OUT:    full document with books[0].year now 1900

QUERY:  $.books[*].tags.append("read")
OUT:    full document with "read" added to every book's tags

QUERY:  $.books[0].unset(tags)
OUT:    full document with books[0].tags removed

Multiple writes in one query batch through a single fused pass.

13. Engine entrypoint (Rust)

use jetro::JetroEngine;
use serde_json::json;

let eng = JetroEngine::default();
let doc = json!({"x":[1,2,3,4,5]});
let v = eng.collect_value(doc, "$.x.filter(@ > 2).sum()")?;
assert_eq!(v, json!(12));

That's the tour. Next: the Grammar Overview, or skip straight to the Builtin Index.