Skip to content

mitghi/jetrocli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jetrocli

New to jetro? jetro-book is the best place to start — guide, tour, and documentation for learning the expression language.

jetrocli

jetrocli is a terminal companion for jetro, a JSON expression language. It gives you two ways to work with JSON: an interactive TUI for exploring data and a command-line program for processing JSON in scripts, pipes, files, and NDJSON datasets.

Run it directly and it opens a split-pane JSON workbench: paste or load JSON, write a jetro expression, and see the result update live as you type. Pipe or redirect input and it skips the TUI, evaluates once, and prints the result like a regular Unix command.

For large files, jetrocli can memory map regular-file input. In --ndjson mode it scans one JSON document per line, supports reverse reads from the end of log-style files, and can stop early with --limit for bounded queries over very large datasets.

Features

  • Live evaluation — expression re-runs on every keystroke.
  • Syntax-highlighted JSON result with pretty-print (strings, numbers, keys, booleans, null distinct).
  • Structural folding in JSON editor. Fold any {…} / […] block, with gutter triangles ( / ) and inline ⋯ N lines markers.
  • Schema-aware completion — suggests fields at the current path, auto-unwraps element fields inside array chains, filters builtins by receiver type.
  • Inline docs pane next to completions — every jetro builtin ships with signature, summary, and example.
  • Pipe / batch mode without TUI — when stdin is piped or redirected, jetrocli evaluates once and prints the result directly for shell workflows.
  • File-backed large JSON reads — regular-file stdin is memory mapped for zero-copy loading instead of forcing the interactive path.
  • Fast NDJSON scans--ndjson evaluates one JSON document per line from -i <FILE> and emits one compact result per row.
  • Reverse NDJSON reads--ndjson -r scans from tail to head, useful for log-style files where the newest rows matter first.
  • Bounded NDJSON filters — combine --ndjson with --limit <N> to stop after the first N emitted rows, including reverse scans for "latest matching rows" queries.
  • Emacs-style bindings throughout (C-a/C-e, C-f/C-b, M-f/M-b, C-n/C-p, C-g, C-c prefix chord).
  • Expression formatter — breaks long jetro chains onto indented lines (C-c C-f).

Install

Homebrew (macOS / Linux)

brew tap mitghi/jetrocli https://github.com/mitghi/homebrew-jetrocli
brew install jetrocli

Shell installer

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/mitghi/jetrocli/releases/latest/download/jetrocli-installer.sh | sh

From source

git clone https://github.com/mitghi/jetrocli
cd jetrocli
cargo build --release

Binary lands at target/release/jetrocli.

Usage

Interactive TUI

Default when stdin is a TTY.

jetrocli                            # sample document
jetrocli -i data.json               # load from file
jetrocli -i data.json -e '$.users'  # pre-fill expression

Pipe / batch mode

When stdin is piped or redirected, TUI is skipped — jetrocli evaluates the expression against stdin and prints the result with jq-style colorized JSON (ANSI dropped when stdout not a TTY; respects NO_COLOR and JETROCLI_COLOR=never).

echo '{"users":[{"name":"a"},{"name":"b"}]}' | jetrocli '$.users.name'
curl -s api.example.com/data | jetrocli '$.items.first()'

With no expression (or empty string), jetrocli just pretty-prints stdin as JSON, like jq with no filter:

cat data.json | jetrocli
echo '{"a":1,"b":[2,3]}' | jetrocli ''

When stdin is backed by a regular file (jetrocli EXPR < big.json), input is mmap'd instead of streamed — zero-copy load for large documents. Real pipes fall back to a buffered read.

NDJSON mode (file-only)

--ndjson switches jetrocli into newline-delimited JSON batch mode: one JSON document per line in -i <FILE>, expression evaluated independently per row, one compact result per output line. File input only.

jetrocli --ndjson -i events.ndjson -e '$.user'
jetrocli --ndjson -i events.ndjson --limit 100 -e '$.level == "error"'
jetrocli --ndjson -i app.log -r -e '$.msg'                    # tail → head
jetrocli --ndjson -i app.log -r --limit 50 -e '$.msg'         # last 50 matches
jetrocli --ndjson -i topic.log --payload-after '|' -e '$.id'  # key|JSON payload

Use $.rows() when the expression should operate on the whole NDJSON file as one stream instead of running independently per line:

jetrocli --ndjson -i events.ndjson \
  -e '$.rows().filter($.active).take(10).map({id: $.id, name: $.name})'

For file inputs, $.rows().reverse() scans from the end of the file. This is useful for logs and Kafka compacted-topic dumps where the newest record for a key is last:

jetrocli --ndjson -i events.ndjson \
  -e '$.rows().reverse().distinct_by($.id).take(100).map({id: $.id, ts: $.ts})'

On the 1 GB benchmark described below, simple row-local projections are typically tens of times faster than jaq, and the best direct byte paths are near 100x faster. Whole-stream $.rows() queries keep the same mmap/direct-byte foundation, but performance depends on how much of the file must be scanned: take(...) and reverse latest-by-key style queries can stop early, while broad filter, distinct_by, or fallback expressions naturally pay for every row they inspect.

Flag Effect
--ndjson Enable NDJSON mode. Requires -i <FILE> and a non-empty expression.
-r, --reverse Read file from tail to head via mmap. Requires --ndjson.
--limit <N> Stop after N emitted rows. Requires --ndjson and N ≥ 1.
--payload-after <SEP> Treat each line as prefix<SEP>JSON and query only the JSON payload after the one-byte separator. Non-payload rows, empty payloads, and skipped null tombstones are ignored.
--null-payload <skip|keep|error> Policy for framed null payloads when --payload-after is used. Defaults to skip.
--max-line-bytes <BYTES> Per-line byte cap. Default 64 MiB.
--reverse-chunk <BYTES> Reverse reader chunk size. Tune for very wide rows.

Performance

NDJSON mode is built for file-backed batch scans. It memory maps the input file, evaluates the expression independently for each line, and emits compact JSON results without starting the interactive TUI.

The repository includes a reproducible benchmark in benchmark/:

rustc -O benchmark/gen_ndjson.rs -o /tmp/gen_ndjson
/tmp/gen_ndjson /tmp/big.ndjson 1000000000
benchmark/bench.sh

benchmark/bench.sh compares only the jetrocli and jaq program. The generator writes roughly 1 GB of NDJSON shaped like:

{"id":1,"name":"user_1","attributes":[{"key":"k1","value":"v_1_1"}]}

One run on an Apple M1 laptop over 4,764,404 rows produced:

Query jetro expression jaq expression jetrocli jaq Speedup
Project id $.id .id 0.72s 27.89s 38.7x
Project name $.name .name 0.30s 28.99s 96.6x
Attributes count $.attributes.len() .attributes | length 1.52s 28.19s 18.5x
Attribute keys list $.attributes.map(@.key) .attributes | map(.key) 2.06s 39.98s 19.4x
First attr value $.attributes.first().value .attributes[0].value 0.80s 29.22s 36.5x
Last attr value $.attributes.last().value .attributes[-1].value 1.61s 29.25s 18.2x
Uppercase name $.name.upper() .name | ascii_upcase 0.41s 28.42s 69.3x
[key,value] pairs $.attributes.map([@.key, @.value]) .attributes | map([.key, .value]) 2.99s 54.26s 18.1x
Count attrs matching _3 $.attributes.filter(@.value.contains("_3")).len() [.attributes[] | select(.value | contains("_3"))] | length 1.52s 48.15s 31.7x
Object keys $.keys() keys 1.05s 28.48s 27.1x

In practice, expect NDJSON mode to be especially strong for field projection, string transforms, row-local indexing, and shallow array operations over large files. Queries that allocate larger derived arrays or inspect more nested values naturally move more bytes and take longer.

License

MIT

About

Jetrocli - A fast, expressive JSON and NDJSON processor for the terminal

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors