jq vs gron vs jless for JSON Exploration With Examples
Compare jq, gron and jless for exploring JSON: querying, grep-able paths and an interactive viewer, with examples on the same document and when to use each.

Faced with a 5,000-line JSON response and a need for one field, the first question is "where is it?", and only then how to extract it. jq answers the second well and the first only if you already know the structure. Two smaller tools fill the first gap. All three are free, single binaries, and they combine. The scripting side is covered in jq for API responses and gron and jq for exploring JSON. The examples assume jq 1.7, gron 0.7 and jless 0.9.
The sample document
{
"order": {
"id": 42,
"status": "shipped",
"customer": {"name": "Ada", "emails": ["[email protected]", "[email protected]"]},
"items": [
{"sku": "A-1", "qty": 2, "price": 9.5},
{"sku": "B-7", "qty": 1, "price": 20}
]
}
}Save it as order.json. The structure is small enough to see; in real responses, nesting is deep and field names repeat.
jq: query and transform
jq '.order.status' order.json
jq -r '.order.items[] | "\(.sku) \(.qty * .price)"' order.json
jq '[.order.items[].qty] | add' order.json
jq '.. | .sku? // empty' order.json"shipped"
A-1 19
B-7 20
3
"A-1"
"B-7"
jq is a full language: paths, filters, arithmetic, grouping, formatting. The recursive descent .. with .sku? finds every sku at any depth, a handy trick when the location is unknown. The cost is the learning curve and the need to know the shape; a typo in a path silently returns null. It is the right tool in scripts, where you write the query once and run it many times.
gron: make JSON greppable
gron order.json | head -8
gron order.json | grep -i sku
gron order.json | grep 'customer.emails' | gron -ujson = {};
json.order = {};
json.order.customer = {};
json.order.customer.emails = [];
json.order.customer.emails[0] = "[email protected]";
json.order.customer.emails[1] = "[email protected]";
json.order.id = 42;
json.order.items = [];
json.order.items[0].sku = "A-1";
json.order.items[1].sku = "B-7";
{
"order": {
"customer": {
"emails": [
"[email protected]",
"[email protected]"
]
}
}
}
gron turns a document into one assignment per leaf, so ordinary text tools work: grep finds a value and shows its full path, diff compares two documents line by line, sed edits values. gron -u converts back to JSON. The output shows the paths you would then use in a jq filter, which is why it is the best answer to "where is that field?". It reads the whole document into memory; it is aimed at exploration, not production pipelines.
jless: browse interactively
jless order.json
curl -s https://api.example.test/orders/42 | jless{
▼ "order": {
"id": 42,
"status": "shipped",
▶ "customer": {…},
▼ "items": [
▶ {…},
▶ {…}
]
}
}
jless is a viewer with vim-style keys: move with j and k, expand and collapse nodes with Enter or h and l, search with /, and copy the path of the highlighted value with yp, in a form that you can paste into jq (for example .order.items[0].sku). The triangle glyphs in the screen sample are part of its display. It handles large documents well and suits a first look at an unfamiliar API.
A routine that uses all three
curl -s "$API/orders/42" > order.json
jless order.json # look at the shape, copy a path
gron order.json | grep -i price # find every place a price appears
jq -r '.order.items[] | [.sku, .price] | @tsv' order.json # the final, scripted queryExplore with the tool that shows structure, then write the jq filter and keep it in the script. For repeated work on JSON Lines or large inputs, jq's streaming and -c output matter, as in preparing JSON for import. Other tools worth knowing are fx, jaq (a faster jq clone) and jc, which converts the output of common commands to JSON.
What each tool is good at
The three tools solve different problems, and confusion starts when one is asked to do another's job. jq is a query language: you describe the result you want and it computes it. gron is a flattening step: it changes the representation of the data so that line-based tools apply. jless is a viewer: it lets your eyes do the navigation and hands you a path to use elsewhere. Using them in that order, look, locate, then query, takes less time than writing a filter by trial and error against an unknown structure.
Think about the audience for the output as well. A filter that runs inside a deployment script must be exact, checked against edge cases and kept in version control. An exploration at a terminal can be sloppy, since a wrong guess costs a few seconds. gron and jless are tuned for the second case, and jq serves both, which is why it ends up in scripts and the other two stay on the desk.
Handling large files and streams
Input size changes the choice. jq reads a whole document before it starts, so a multi-gigabyte file can exhaust memory; its --stream mode emits path and value events and allows processing in bounded memory, at the cost of a more awkward syntax. For files of newline-delimited JSON, each line is a document, and jq processes them one at a time without special flags, which makes it fine for logs. gron's output is large, since it repeats the path on every line, so a 100 MB document becomes several times larger as text. For such sizes, use jq with a narrow filter, or jq -c to pull out the records you need before handing them to the other tools.
jless reads the whole file too, but it displays lazily and handles large inputs well. If it is slow to open a very large file, pre-filter with jq to the sub-tree of interest and pipe the result into jless. The tools compose through standard input and output, which is the main reason they coexist without overlap.
Precision and edge cases
JSON numbers have no defined precision, and tools differ. jq 1.7 preserves the original text of large integers when they pass through unmodified, but converts to a 64-bit float when arithmetic touches them, so an identifier such as 9007199254740993 changes. If identifiers are numbers in your data, pass them as strings in the filter with tostring early, or read the document with a tool that preserves integers. Also note that jq reorders nothing by default but its -S flag sorts keys, which makes diffs stable, and that duplicate keys in an object are silently collapsed to the last.
Unicode behaves as expected in all three, but terminals may not display every character. Use -r for raw strings when passing values to shell variables, and quote them when expanding, since a value containing spaces or a newline will otherwise split into several arguments.
Fitting them into a team workflow
Put the final jq filters in the repository next to the scripts that use them, with a sample input and the expected output, so that a change to the API shows up as a failing check. Treat paths found in an exploration as a hypothesis. Run the filter against several real responses, including empty results and error responses, before relying on it, and add the optional operator or a default for fields that are not always present. The tools find the path; the care about missing data is still yours.
Where this does not apply
For a quick pretty-print, python3 -m json.tool or jq . is enough. If the data is not JSON (YAML, TOML, XML), use yq, tomlq or xmlstarlet, which follow the same ideas. Never paste production data containing secrets into an online JSON viewer. See the jq manual, the gron repository and the jless site.


