Jsongrep
A path query language for JSON, YAML, TOML, and other serialization formats.
Install / Use
/learn @micahkepe/JsongrepREADME
Why jsongrep?
JSON documents are trees: objects and arrays branch into nested values, with
edges labeled by field names or array indices. jsongrep lets you describe
sets of paths through this tree using regular expression operators - the
same way you'd match patterns in text.
**.name # Kleene star: match "name" under nested objects
users[*].email # Wildcard: all emails in the users array
(error|warn).* # Disjunction: any field under "error" or "warn"
(* | [*])*.name # Any depth: match "name" through both objects and arrays
This is different from tools like jq, which use a filter pipeline to transform
data. With jsongrep, you declare what paths to match rather than describing
how to transform. The query compiles to a
DFA that
processes the document efficiently.
See the blog post for the motivation and design behind jsongrep.
jsongrep vs jq
jq is a powerful tool, but its filter syntax can be verbose for common
path-matching tasks. jsongrep is declarative: you describe the shape of the
paths you want, and the engine finds them.
Find a field at any depth:
# jsongrep: -F treats the query as a literal field name at any depth
$ curl -s https://api.nobelprize.org/v1/prize.json | jg -F firstname | head -6
prizes.[0].laureates.[0].firstname:
"Susumu"
prizes.[0].laureates.[1].firstname:
"Richard"
prizes.[0].laureates.[2].firstname:
"Omar M."
# jq: requires a recursive descent operator and null suppression
$ curl -s https://api.nobelprize.org/v1/prize.json | jq '.. | .firstname? // empty' | head -3
"Susumu"
"Richard"
"Omar M."
jsongrep also shows where each match was found (e.g.,
prizes.[0].laureates.[0].firstname:), which jq does not. (Examples below
show terminal output; when piped, path headers are hidden by default. See
--with-path / --no-path.)
Select multiple fields at once:
# jsongrep: disjunction with (year|category)
$ curl -s https://api.nobelprize.org/v1/prize.json | jg 'prizes[0].(year|category)'
prizes.[0].year:
"2025"
prizes.[0].category:
"chemistry"
# jq: requires listing each field separately
$ curl -s https://api.nobelprize.org/v1/prize.json | jq '.prizes[0] | .year, .category'
"2025"
"chemistry"
Count matches:
# jsongrep
$ curl -s https://api.nobelprize.org/v1/prize.json | jg -F firstname --count -n
Found matches: 1026
# jq
$ curl -s https://api.nobelprize.org/v1/prize.json | jq '[.. | .firstname? // empty] | length'
1026
Pretty-print JSON (like jq '.'):
$ echo '{"name":"Ada","age":36}' | jg ''
{
"name": "Ada",
"age": 36
}
Benchmarks
jsongrep is benchmarked against
jsonpath-rust,
jmespath,
jaq, and
jql using
Criterion. Four benchmark groups isolate
different costs:
| Group | What's measured |
| ---------------- | ------------------------------------------------- |
| document_parse | JSON string → in-memory document |
| query_compile | Query string → compiled query/DFA/filter |
| query_search | Search only (pre-parsed doc + pre-compiled query) |
| end_to_end | Full pipeline: parse + compile + search |
Test data ranges from a small sample JSON to a 190 MB GeoJSON file (citylots.json), with queries chosen to exercise equivalent functionality across tools (recursive descent, wildcards, nested paths). Where a tool lacks a feature, the benchmark is skipped rather than faked.
End-to-end on 190 MB GeoJSON (xlarge):
<p align="center"> <img src="./images/benchmark-xlarge-e2e.png" alt="End-to-end xlarge benchmark violin plot" width="700"/> </p>Interactive Criterion reports | Benchmark source and methodology
Quick Example
# Extract all firstnames from the Nobel Prize API
$ curl -s https://api.nobelprize.org/v1/prize.json | jg 'prizes[0].laureates[*].firstname'
prizes.[0].laureates.[0].firstname:
"Susumu"
prizes.[0].laureates.[1].firstname:
"Richard"
prizes.[0].laureates.[2].firstname:
"Omar M."
# Works with inline JSON too
$ echo '{"users": [{"name": "Alice"}, {"name": "Bob"}]}' | jg 'users.[*].name'
users.[0].name:
"Alice"
users.[1].name:
"Bob"
Multi-Format Input
jg natively supports multiple serialization formats. Non-JSON formats are
converted to JSON at the boundary, then queried with the same engine, so your
queries work identically regardless of input format.
Query your Cargo.toml:
$ jg 'dependencies.*.version' Cargo.toml
dependencies.clap.version:
"4.5.43"
dependencies.serde.version:
"1.0.219"
...
Query a docker-compose.yml:
$ jg 'services.*.image' docker-compose.yml
services.web.image:
"nginx:latest"
services.db.image:
"postgres:16"
JSONL/NDJSON: each line becomes an array element:
$ jg '[*].email' users.jsonl
[0].email:
"alice@example.com"
[1].email:
"bob@example.com"
Explicit format flag (useful for stdin or non-standard extensions):
$ cat config.yaml | jg -f yaml 'database.host'
database.host:
"localhost"
Binary formats (CBOR, MessagePack):
$ jg 'name' data.cbor
$ jg -f msgpack 'name' data.bin
| Format | Extensions | Feature flag | Notes |
| ------------ | ------------------- | ------------ | ----------------------- |
| JSON | .json (default) | — | Always available |
| JSONL/NDJSON | .jsonl, .ndjson | — | Wrapped into JSON array |
| YAML | .yaml, .yml | yaml | Included by default |
| TOML | .toml | toml | Included by default |
| CBOR | .cbor | cbor | Included by default |
| MessagePack | .msgpack, .mp | msgpack | Included by default |
All format dependencies are included by default. To build without them:
cargo install jsongrep --no-default-features
Installation
<p align="center"> <a href="https://repology.org/project/jsongrep/versions"> <img src="https://repology.org/badge/vertical-allrepos/jsongrep.svg" alt="Packaging status"> </a> </p>Installing with Cargo:
cargo install jsongrep
The jg binary installs to ~/.cargo/bin.
Alternatively, you can install jsongrep using Homebrew:
brew install jsongrep
The jg binary installs to either /opt/homebrew (Apple Silicon) or
/usr/local (Intel).
CLI Usage
JSONPath-inspired query language for JSON, YAML, TOML, and other serialization formats
Usage: jg [OPTIONS] [QUERY] [FILE] [COMMAND]
Commands:
generate Generate additional documentation and/or completions
Arguments:
[QUERY] Query string (e.g., "**.name")
[FILE] Optional path to file. If omitted, reads from STDIN
Options:
-i, --ignore-case Case insensitive search
--compact Do not pretty-print the JSON output
--count Display count of number of matches
--depth Display depth of the input document
-n, --no-display Do not display matched JSON values
-F, --fixed-string Treat the query as a literal field name and search at any depth
--with-path Always print the path header, even when output is piped
--no-path Never print the path header, even in a terminal
-f, --format <FORMAT> Input format (auto-detects from file extension if omitted) [default: auto] [possible values: auto, json, jsonl, yaml, toml, cbor, msgpack]
-h, --help Print help (see more with '--help')
-V, --version Print version
More CLI Examples
Search for a literal field name at any depth:
curl -s https://api.nobelprize.org/v1/prize.json | jg -F motivation | head -4
Count matches without displaying them:
curl -s https://api.nobelprize.org/v1/prize.json | jg -F firstname --count -n
# Found matches: 1026
Piping to other tools:
By default, path headers display in terminals and hide when output is piped
(like ripgrep's --heading). This makes piping to sort, uniq, etc., work
cleanly:
# Piped: values only, ready for sort/uniq/wc
$ curl -s https://api.nobelprize.org/v1/prize.json | jg -F firstname | sort | head -3
"A. Michael"
"Aage N."
"Aaron"
# Force path headers when piped
$ curl -s https://api.nobelprize.org/v1/prize.json | jg -F firstname --with-path | head -4
prizes.[0].laureates.[0].firstname:
"Susumu"
prizes.[0].laureates.[1].firstname:
"Richard"
Query Syntax
Queries are regular expressions over paths. If you kn
Related Skills
node-connect
339.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR
