gron Makes JSON Greppable: Flatten Deep JSON Into Searchable Assignment Lines
How gron rewrites JSON as one path-assignment per line so grep can find any deep value, how ungron rebuilds the JSON, and why it speeds up API debugging.
gron Makes JSON Greppable: Flatten Deep JSON Into Searchable Assignment Lines
I keep a tab open on a tool I reach for almost every day when an API hands me a giant JSON blob and I have no idea where the value I want is hiding. The tool is gron, and the idea behind it is small enough to explain in one sentence: rewrite the JSON so every value sits on its own line, prefixed with the full path that reaches it. Once the data looks like that, grep works on JSON the same way it works on a log file. No jq filters, no scrolling through nested braces, no guessing the shape.
This post walks through what gron does, why the flat format makes deep JSON searchable, how the reverse direction (ungron) rebuilds the original document, and the specific debugging moves I use it for.
What gron actually produces
gron turns each JSON value into a full path assignment. Object keys join with dots, array elements use [index], and the value on the right is a JSON literal — strings keep their quotes, numbers, booleans and null stay bare. So a nested user record becomes a line like:
json.users[0].name = "Ada";
Read that left to right and it tells you everything: start at the root variable json, go into users, take element 0, read its name field, and the value is the string "Ada". Every leaf in the document gets exactly one of these lines, plus a declaration line for each container it passes through. That is the whole format. There are no braces to balance, no indentation to track, and nothing is implied — the path is spelled out in full on every single line.
Keys that are not plain identifiers get bracket-quoted, so a key with a space or a dash becomes json["weird key"] instead of json.weird key. That detail matters because it keeps each line unambiguous and machine-rebuildable, which is what lets the reverse direction work.
Why one-leaf-per-line makes JSON searchable
The reason this format is useful is mechanical, not aesthetic. grep is a line-oriented tool. It finds lines that match a pattern and prints them. Ordinary pretty-printed JSON defeats it: a value and the key that names it sit on different lines, and the path that reaches them is scattered across the indentation above. Grepping for token in raw JSON gives you the line with the key but not where in the tree it lives.
gron collapses all of that onto one line per value, so a match carries its own context. Pipe the output into grep and every hit is a complete, located fact:
gron api.json | grep -i token
json.data.session.refresh_token = "eyJhbGci...";
json.data.session.access_token = "eyJ0eXAi...";
You instantly know not just that a token exists but exactly where it lives in the structure. The same trick isolates any field: grep email, grep -i id, or grep '\[3\]' to look only at the fourth array element. Add grep -c to count matches. Because the format is flat and stable, you can reach for the line-oriented tools you already know — grep, sed, awk, sort, diff — and they all just work on JSON.
A worked example
Take this small JSON document:
{
"name": "toolora",
"tags": ["json", "cli"],
"stars": 42,
"active": true,
"owner": null
}
Run it through gron and you get:
json = {};
json.name = "toolora";
json.tags = [];
json.tags[0] = "json";
json.tags[1] = "cli";
json.stars = 42;
json.active = true;
json.owner = null;
Notice the shape of it. Containers get a declaration (json = {}; and json.tags = [];) so the structure can be reconstructed, and every leaf is a plain assignment. The string values keep their quotes; 42, true and null are bare, so the lines read like real code. Now grep tags returns three lines that tell you the array exists, has length two, and holds "json" and "cli". You read the answer instead of parsing it.
ungron: rebuilding the JSON
The format is designed to round-trip, and that is what the reverse direction does. ungron reads gron lines such as json.tags[0] = "json"; and walks each path, creating the objects and arrays it needs along the way, then dropping the JSON literal into place. Feed it the eight lines above and you get the original document back, exactly — same keys, same types, same nesting. The round trip is lossless.
That reversibility is the part that turns gron from a viewer into a workflow. The loop is: gron the file, edit one line with a text editor or sed, ungron it back. Say you want to bump json.config.replicas from 2 to 5 in a config buried three levels deep. Rather than hand-editing through the nesting and risking a misplaced comma, you flatten the file, change the single unambiguous line, and rebuild valid, pretty-printed JSON from it. The path syntax makes the edit impossible to misread, and the rebuild guarantees the structure comes back correct.
One thing to keep straight: gron output is not valid JSON. It is a list of assignment statements. Do not feed gron lines into a JSON parser — switch the direction to ungron to get JSON back. And when you hand-edit a line, the right side still has to be a JSON literal: a string needs its quotes (= "5";), a bare number is = 5;, and writing = 5 abc; will fail to ungron with a value error.
Where it pays off: debugging API responses
The case I hit most often is a 4000-line API payload where I need one value — an auth token, a session id, a redirect URL — and I have no map of the response. Paste it into the gron Converter, switch to JSON-to-gron, copy the lines, and grep the word I care about. The path falls out on one line and I'm done. The same move surfaces every URL, every id, every email in a response I've never seen before, without learning its schema first.
The second case is diffing. Structured JSON diffs are noisy because formatting and key order shuffle things around, so a reordered object can look like a hundred-line change. Run both files through gron and the output is a flat, stable list of path = value lines. An ordinary line diff (or git diff) then shows exactly which leaves changed — the genuinely different assignments stand out and the reordering noise disappears.
It also helps when you're explaining a JSON shape to someone else. In a doc or a code review, a wall of braces hides the structure; a gron line like json.items[0].price = 9.99; spells out the path, the type and the location in one read, with no mental indentation parsing required.
Where gron fits among JSON tools
gron is not a flattener and not a formatter, even though it touches both jobs. If you want a flat map of dot-keys like {"a.b": 1} to load into code, that's json-flatten — a flat object, not assignment statements built to rebuild. If your JSON is minified and you just want it readable with proper indentation, reach for a JSON formatter instead. gron's niche is the command-line one: make deep JSON greppable with the line tools you already have, then reconstruct it afterward. When that's the job — find a value fast, edit it safely, rebuild it exactly — flat assignment lines beat every nested view.
Everything runs in your browser. The JSON you paste, the gron lines and the rebuilt output never leave the page. The only caveat is the shareable URL, which encodes your input in the link, so for anything sensitive like tokens use the copy button rather than sharing the URL.
Made by Toolora · Updated 2026-06-13