GRON(1)
πΈ NAME πΈ
okay so gron(1) is literally the bestie who transforms JSON into discrete, greppable assignments. That's the whole point. It makes JSON less of a cryptic mess and more of a "oh I can actually find that thing" situation. No cap.
β¨ SYNOPSIS β¨
gron [OPTIONS] [FILE|URL]
gron β-version
gron β-help
π DESCRIPTION π
gron takes JSON and breaks it into discrete assignments, so you can grep for exactly what you want and see the absolute path to it. It's giving "finally, I can explore this API without crying." It especially eats for APIs that return huge blobs of JSON but have documentation that is, respectfully, terrible. π
π₯ OPTIONS π₯
-u,--ungronβ reverse the operation (turn assignments back into JSON).-v,--valuesβ print just the values of the assignments you provided.-c,--colorizeβ colorize output (default on tty).-m,--monochromeβ monochrome mode: don't colorize output.-s,--streamβ treat each line of input as a separate JSON object.-k,--insecureβ disable certificate validation. (Careful, bestie.)-j,--jsonβ represent gron data as a JSON stream.--no-sortβ don't sort output (faster).--versionβ print version information.
π BASIC USAGE π
Get JSON from a file:
$ gron testdata/two.json
json = {};
json.contact = {};
json.contact.email = "mail@tomnomnom.com";
json.contact.twitter = "@TomNomNom";
json.github = "https://github.com/tomnomnom/";
json.likes = [];
json.likes[0] = "code";
json.likes[1] = "cheese";
json.likes[2] = "meat";
json.name = "Tom";
From a URL:
$ gron http://headers.jsontest.com/
json = {};
json.Host = "headers.jsontest.com";
json["User-Agent"] = "gron/0.1";
json["X-Cloud-Trace-Context"] = "6917a823919477919dbc1523584ba25d/11970839830843610056";
Or from STDIN:
$ curl -s http://headers.jsontest.com/ | gron
json = {};
json.Accept = "*/*";
json.Host = "headers.jsontest.com";
json["User-Agent"] = "curl/7.43.0";
json["X-Cloud-Trace-Context"] = "c70f7bf26661c67d0b9f2cde6f295319/13941186890243645147";
Grep for something and easily see the path to it:
$ gron testdata/two.json | grep twitter
json.contact.twitter = "@TomNomNom";
gron makes diffing JSON easy too:
$ diff <(gron two.json) <(gron two-b.json)
3c3
< json.contact.email = "mail@tomnomnom.com";
---
> json.contact.email = "contact@tomnomnom.com";
The output of gron is valid JavaScript:
$ gron testdata/two.json > tmp.js
$ echo "console.log(json);" >> tmp.js
$ nodejs tmp.js
{ contact: { email: 'mail@tomnomnom.com', twitter: '@TomNomNom' },
github: 'https://github.com/tomnomnom/',
likes: [ 'code', 'cheese', 'meat' ],
name: 'Tom' }
Itβs also possible to obtain the gron output as JSON stream via the --json switch:
$ curl -s http://headers.jsontest.com/ | gron --json
[[],{}]
[["Accept"],"*/*"]
[["Host"],"headers.jsontest.com"]
[["User-Agent"],"curl/7.43.0"]
[["X-Cloud-Trace-Context"],"c70f7bf26661c67d0b9f2cde6f295319/13941186890243645147"]
π¦ UNGRONNING π¦
gron can also turn its output back into JSON:
$ gron testdata/two.json | gron -u
{
"contact": {
"email": "mail@tomnomnom.com",
"twitter": "@TomNomNom"
},
"github": "https://github.com/tomnomnom/",
"likes": [
"code",
"cheese",
"meat"
],
"name": "Tom"
}
Which means you can totally use gron with grep and other tools to modify JSON:
$ gron testdata/two.json | grep likes | gron --ungron
{
"likes": [
"code",
"cheese",
"meat"
]
}
or
$ gron --json testdata/two.json | grep likes | gron --json --ungron
{
"likes": [
"code",
"cheese",
"meat"
]
}
To preserve array keys, arrays are padded with null when values are missing:
$ gron testdata/two.json | grep likes | grep -v cheese
json.likes = [];
json.likes[0] = "code";
json.likes[2] = "meat";
βΆ gron testdata/two.json | grep likes | grep -v cheese | gron --ungron
{
"likes": [
"code",
null,
"meat"
]
}
π ADVANCED USAGE π
okay bestie so gron's whole thing is API discovery, that's the main character energy right there. but the way that it slaps when you pair it with other tools like grep? immaculate. π
so as a lil exercise, let's try to mimic some of the examples from the jq tutorial (https://stedolan.github.io/jq/tutorial/). π§
Disclaimer: munging data on the command line with gron can be useful, but using tools likegrepandsedto manipulate the data is error-prone and shouldn't be relied on in scripts.
no fr, listen to the disclaimer. it's giving "fun for vibes, not for prod." π
πΈ Get the last 5 commits from the gron repo πΈ
$ gron "https://api.github.com/repos/tomnomnom/gron/commits?per_page=5"
json = [];
json[0] = {};
json[0].author = {};
json[0].author.avatar_url = "https://avatars.githubusercontent.com/u/58276?v=3";
json[0].author.events_url = "https://api.github.com/users/tomnomnom/events{/privacy}";
...
json[4].parents[0].html_url = "https://github.com/tomnomnom/gron/commit/cbcad2299e55c28a9922776e58b2a0b5a0f05016";
json[4].parents[0].sha = "cbcad2299e55c28a9922776e58b2a0b5a0f05016";
json[4].parents[0].url = "https://api.github.com/repos/tomnomnom/gron/commits/cbcad2299e55c28a9922776e58b2a0b5a0f05016";
json[4].sha = "91b204972e63a1166c9d148fbbfd839f8697f91b";
json[4].url = "https://api.github.com/repos/tomnomnom/gron/commits/91b204972e63a1166c9d148fbbfd839f8697f91b";
to make the rest of this a lil more readable, let's add an alias for that whole thing. self-care via shell aliases, honestly:
$ alias ggh='gron "https://api.github.com/repos/tomnomnom/gron/commits?per_page=5"'
π Extract just the first commit using fgrep "json[0]" π
$ ggh | fgrep "json[0]"
json[0] = {};
json[0].author = {};
json[0].author.avatar_url = "https://avatars.githubusercontent.com/u/58276?v=3";
json[0].author.events_url = "https://api.github.com/users/tomnomnom/events{/privacy}";
json[0].author.followers_url = "https://api.github.com/users/tomnomnom/followers";
...
json[0].parents[0].html_url = "https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311";
json[0].parents[0].sha = "48aba5325ece087ae24ab72684851cbe77ce8311";
json[0].parents[0].url = "https://api.github.com/repos/tomnomnom/gron/commits/48aba5325ece087ae24ab72684851cbe77ce8311";
json[0].sha = "7da81e29c27241c0a5c2e5d083ddebcfcc525908";
json[0].url = "https://api.github.com/repos/tomnomnom/gron/commits/7da81e29c27241c0a5c2e5d083ddebcfcc525908";
π Get just the committer's name and the commit message using egrep "(committer.name|commit.message)" π
$ ggh | fgrep "json[0]" | egrep "(committer.name|commit.message)"
json[0].commit.committer.name = "Tom Hudson";
json[0].commit.message = "Adds 0.1.7 to changelog";
π¦ Turn the result back into JSON using gron --ungron π¦
βΆ ggh | fgrep "json[0]" | egrep "(committer.name|commit.message)" | gron --ungron
[
{
"commit": {
"committer": {
"name": "Tom Hudson"
},
"message": "Adds 0.1.7 to changelog"
}
}
]
gron preserves the location of values in the JSON, but you can use sed to remove keys from the path. path surgery era β¨:
$ ggh | fgrep "json[0]" | egrep "(committer.name|commit.message)" | sed -r "s/(commit|committer)\.//g"
json[0].name = "Tom Hudson";
json[0].message = "Adds 0.1.7 to changelog"
with those keys removed, the result is a flattened object, which looks much cleaner when turned back into JSON with gron --ungron:
$ ggh | fgrep "json[0]" | egrep "(committer.name|commit.message)" | sed -r "s/(commit|committer)\.//g" | gron --ungron
[
{
"message": "Adds 0.1.7 to changelog",
"name": "Tom Hudson"
}
]
π Removing the fgrep "json[0]" from the pipeline means we do the same for all commits π
$ ggh | egrep "(committer.name|commit.message)" | sed -r "s/(commit|committer)\.//g" | gron --ungron
[
{
"message": "Adds 0.1.7 to changelog",
"name": "Tom Hudson"
},
{
"message": "Refactors natural sort to actually work + be more readable",
"name": "Tom Hudson"
},
...
to include the html_url key for each commit's parents, all we need to do is add parents.*html_url into our call to egrep. one lil addition, huge payoff π₯:
$ ggh | egrep "(committer.name|commit.message|parents.*html_url)" | sed -r "s/(commit|committer)\.//g"
json[0].name = "Tom Hudson";
json[0].message = "Adds 0.1.7 to changelog";
json[0].parents[0].html_url = "https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311";
json[1].name = "Tom Hudson";
json[1].message = "Refactors natural sort to actually work + be more readable";
json[1].parents[0].html_url = "https://github.com/tomnomnom/gron/commit/3eca8bf5e07151f077cebf0d942c1fa8bc51e8f2";
...
to make the structure more like that in the final example in the jq tutorial, we can use sed -r "s/\.html_url//" to remove the .html_url part of the path:
βΆ ggh | egrep "(committer.name|commit.message|parents.*html_url)" | sed -r "s/(commit|committer)\.//g" | sed -r "s/\.html_url//"
json[0].name = "Tom Hudson";
json[0].message = "Adds 0.1.7 to changelog";
json[0].parents[0] = "https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311";
json[1].name = "Tom Hudson";
json[1].message = "Refactors natural sort to actually work + be more readable";
json[1].parents[0] = "https://github.com/tomnomnom/gron/commit/3eca8bf5e07151f077cebf0d942c1fa8bc51e8f2";
...
and, of course, the statements can be turned back into JSON with gron --ungron. the full pipeline. the finale. the slay. π
$ ggh | egrep "(committer.name|commit.message|parents.*html_url)" | sed -r "s/(commit|committer)\.//g" | sed -r "s/\.html_url//" | gron --ungron
[
{
"message": "Adds 0.1.7 to changelog",
"name": "Tom Hudson",
"parents": [
"https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311"
]
},
{
"message": "Refactors natural sort to actually work + be more readable",
"name": "Tom Hudson",
"parents": [
"https://github.com/tomnomnom/gron/commit/3eca8bf5e07151f077cebf0d942c1fa8bc51e8f2"
]
},
...
β¨ EXAMPLES β¨
π Read from a local file/network π
$ gron /tmp/apiresponse.json
$ gron http://jsonplaceholder.typicode.com/users/1
π Retrieve remote JSON and pipe through gron π
$ curl -s http://jsonplaceholder.typicode.com/users/1 | gron
π Flatten and filter JSON through gron, before turning result back into JSON π
$ gron http://jsonplaceholder.typicode.com/users/1 | grep company | gron --ungron
π TIPS π
okay this one's a quality of life upgrade, bestie: it's recommended that you alias ungron or norg (or both!) to gron --ungron. drop something like this in your shell profile (e.g. in ~/.bashrc):
alias norg="gron --ungron"
alias ungron="gron --ungron"
or you could create a shell script in your $PATH named ungron or norg to affect all users:
> ngl that last snippet is literally exactly how it shows up in the page, funky backticks and all. <yellow>iykyk</yellow> π
---
# π EXIT STATUS π
the vibes, numerically:
- `0` OK
- `1` Failed to open file
- `2` Failed to read input
- `3` Failed to form statements
- `4` Failed to fetch URL
- `5` Failed to parse statements
- `6` Failed to encode JSON
---
# π«Ά REPORTING BUGS π«Ά
found something cursed? upstream bug tracker: <rainbow>https://github.com/tomnomnom/gron/issues</rainbow> β go tell them, they're nice about it fr fr.
---
# π COPYRIGHT π
Copyright (c) 2016 Tom Hudson
---
# π§ AUTHOR π§
this manual page is based on the gron documentation. it was created by **Nick Morrott** <nickm@debian.org> for the Debian GNU/Linux system, but may be used by others. <lavender>shoutout Nick, absolute icon</lavender> π
---
# π SEE ALSO π
`grep(1)`, `ack(1)`, `ag(1)`, `rg(1)`, `jq(1)`
---
and that's the whole page, bestie!! π this is <pink>gron 0.7.1</pink>, dated <cyan>April 2022</cyan>, and it's officially a wrap. go flatten some JSON and feel like the main character you are. periodt. π β¨ π¦
π¦ where this page comes from
- ubuntu noble: gron 0.7.1-2 Β· MIT
- ubuntu noble: gron 0.7.1-2ubuntu0.24.04.3 Β· MIT
- ubuntu questing: gron 0.7.1-2
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms