2026-04-25
You have a 500-line JSON blob from some API. You need to find where the hell the email field is buried. You could pipe it through jq and write increasingly frustrated path expressions, or you could do what a sane person does: grep for it.
gron transforms JSON into discrete assignments — one per line — so you can use grep, sed, awk, and every other line-oriented Unix tool on structured data. Then gron --ungron turns it back into JSON.
Install it:
go install github.com/tomnomnom/gron@latest
# or: brew install gron / apt install gron
Here's the core idea. Given this JSON:
{"users":[{"name":"shaun","roles":["admin","ops"],"contact":{"email":"[email protected]"}}]}
Running gron produces:
$ echo '{"users":[{"name":"shaun","roles":["admin","ops"],"contact":{"email":"[email protected]"}}]}' | gron
json = {};
json.users = [];
json.users[0] = {};
json.users[0].name = "shaun";
json.users[0].roles = [];
json.users[0].roles[0] = "admin";
json.users[0].roles[1] = "ops";
json.users[0].contact = {};
json.users[0].contact.email = "[email protected]";
Now you can grep. Want to find every path that mentions "email" anywhere in a gnarly Kubernetes manifest or Terraform state file?
$ cat terraform.tfstate | gron | grep -i email
json.resources[3].instances[0].attributes.email = "[email protected]";
json.resources[7].instances[0].attributes.notification_email = "[email protected]";
You just found the exact JSON path without knowing the structure in advance. Try doing that with jq without recursive descent expressions that look like line noise.
The round-trip trick is where it gets deadly. Filter the assignments, then ungron back to valid JSON:
$ curl -s https://api.github.com/repos/torvalds/linux | gron | grep -i "url" | gron --ungron
{
"archive_url": "https://api.github.com/repos/torvalds/linux/{archive_format}{/ref}",
"assignees_url": "https://api.github.com/repos/torvalds/linux/assignees{/user}",
...
}
You just extracted a subset of the JSON using grep. No jq gymnastics, no Python scripts — just pipes.
Diffing JSON becomes trivial:
$ diff <(gron old-config.json) <(gron new-config.json)
42c42
< json.database.pool_size = 5;
---
> json.database.pool_size = 20;
57a58
> json.features.dark_mode = true;
Compare that to diff on raw JSON, where a single reordered key makes the entire output useless.
Modifying JSON with sed:
$ gron config.json | sed 's/json.database.host = .*/json.database.host = "db-prod.internal";/' | gron --ungron > config-prod.json
You just did a surgical JSON edit with sed. No jq update syntax, no Python, no node one-liners.
Finding all leaf values:
$ gron huge.json | grep -v '= \[\]' | grep -v '= {}' | wc -l
This isn't a replacement for jq — when you know the path and want to extract or transform, jq is king. But gron solves the discovery problem: "where is this value?", "what changed?", "what does this structure even look like?" It turns an opaque tree into lines, and Unix already has 50 years of tools for working with lines.
It handles streaming JSON (--stream), works on URLs directly (gron https://...), and runs fast enough that you'll never notice it. The binary is a single Go executable with no dependencies.
gron flattens JSON into greppable assignments so you can use grep, sed, and diff on structured data, then gron --ungron reassembles it — turning JSON exploration from a jq puzzle into a Unix pipeline.
