.\" Automatically generated by Pandoc 2.9.2.1 .\" .TH "GRON" "1" "July 2018" "0.6.1" "User Commands" .hy .SH NAME .PP \f[B]gron\f[R] - transform JSON into discrete, greppable assignments .SH SYNOPSIS .PP \f[B]gron\f[R] [\f[I]OPTIONS\f[R]] [\f[I]FILE\f[R]|\f[I]URL\f[R]] .PP \f[B]gron\f[R] \f[B]\[en]-version\f[R] .PP \f[B]gron\f[R] \f[B]\[en]-help\f[R] .SH DESCRIPTION .PP \f[B]gron\f[R] transforms JSON into discrete assignments to make it easier to \f[B]grep\f[R] for what you want and see the absolute `path' to it. It eases the exploration of APIs that return large blobs of JSON but have terrible documentation. .SH OPTIONS .TP -u, --ungron reverse the operation (turn assignments back into JSON) .TP -c, --colorize colorize output (default on tty) .TP -m, --monochrome monochrome (don\[cq]t colorize output) .TP -s, --stream treat each line of input as a separate JSON object .TP -k, --insecure disable certificate validation .TP -j, --json represent gron data as JSON stream .TP --no-sort don\[cq]t sort output (faster) .SH BASIC USAGE .PP Get JSON from a file: .IP .nf \f[C] $ gron testdata/two.json json = {}; json.contact = {}; json.contact.email = \[dq]mail\[at]tomnomnom.com\[dq]; json.contact.twitter = \[dq]\[at]TomNomNom\[dq]; json.github = \[dq]https://github.com/tomnomnom/\[dq]; json.likes = []; json.likes[0] = \[dq]code\[dq]; json.likes[1] = \[dq]cheese\[dq]; json.likes[2] = \[dq]meat\[dq]; json.name = \[dq]Tom\[dq]; \f[R] .fi .PP From a URL: .IP .nf \f[C] $ gron http://headers.jsontest.com/ json = {}; json.Host = \[dq]headers.jsontest.com\[dq]; json[\[dq]User-Agent\[dq]] = \[dq]gron/0.1\[dq]; json[\[dq]X-Cloud-Trace-Context\[dq]] = \[dq]6917a823919477919dbc1523584ba25d/11970839830843610056\[dq]; \f[R] .fi .PP Or from \f[C]STDIN\f[R]: .IP .nf \f[C] $ curl -s http://headers.jsontest.com/ | gron json = {}; json.Accept = \[dq]*/*\[dq]; json.Host = \[dq]headers.jsontest.com\[dq]; json[\[dq]User-Agent\[dq]] = \[dq]curl/7.43.0\[dq]; json[\[dq]X-Cloud-Trace-Context\[dq]] = \[dq]c70f7bf26661c67d0b9f2cde6f295319/13941186890243645147\[dq]; \f[R] .fi .PP Grep for something and easily see the path to it: .IP .nf \f[C] $ gron testdata/two.json | grep twitter json.contact.twitter = \[dq]\[at]TomNomNom\[dq]; \f[R] .fi .PP \f[C]gron\f[R] makes diffing JSON easy too: .IP .nf \f[C] $ diff <(gron two.json) <(gron two-b.json) 3c3 < json.contact.email = \[dq]mail\[at]tomnomnom.com\[dq]; --- > json.contact.email = \[dq]contact\[at]tomnomnom.com\[dq]; \f[R] .fi .PP The output of \f[C]gron\f[R] is valid JavaScript: .IP .nf \f[C] $ gron testdata/two.json > tmp.js $ echo \[dq]console.log(json);\[dq] >> tmp.js $ nodejs tmp.js { contact: { email: \[aq]mail\[at]tomnomnom.com\[aq], twitter: \[aq]\[at]TomNomNom\[aq] }, github: \[aq]https://github.com/tomnomnom/\[aq], likes: [ \[aq]code\[aq], \[aq]cheese\[aq], \[aq]meat\[aq] ], name: \[aq]Tom\[aq] } \f[R] .fi .PP It\[cq]s also possible to obtain the \f[C]gron\f[R] output as JSON stream via the \f[C]--json\f[R] switch: .IP .nf \f[C] $ curl -s http://headers.jsontest.com/ | gron --json [[],{}] [[\[dq]Accept\[dq]],\[dq]*/*\[dq]] [[\[dq]Host\[dq]],\[dq]headers.jsontest.com\[dq]] [[\[dq]User-Agent\[dq]],\[dq]curl/7.43.0\[dq]] [[\[dq]X-Cloud-Trace-Context\[dq]],\[dq]c70f7bf26661c67d0b9f2cde6f295319/13941186890243645147\[dq]] \f[R] .fi .SH UNGRONNING .PP \f[C]gron\f[R] can also turn its output back into JSON: .IP .nf \f[C] $ gron testdata/two.json | gron -u { \[dq]contact\[dq]: { \[dq]email\[dq]: \[dq]mail\[at]tomnomnom.com\[dq], \[dq]twitter\[dq]: \[dq]\[at]TomNomNom\[dq] }, \[dq]github\[dq]: \[dq]https://github.com/tomnomnom/\[dq], \[dq]likes\[dq]: [ \[dq]code\[dq], \[dq]cheese\[dq], \[dq]meat\[dq] ], \[dq]name\[dq]: \[dq]Tom\[dq] } \f[R] .fi .PP This means you use can use \f[C]gron\f[R] with \f[C]grep\f[R] and other tools to modify JSON: .IP .nf \f[C] $ gron testdata/two.json | grep likes | gron --ungron { \[dq]likes\[dq]: [ \[dq]code\[dq], \[dq]cheese\[dq], \[dq]meat\[dq] ] } \f[R] .fi .PP or .IP .nf \f[C] $ gron --json testdata/two.json | grep likes | gron --json --ungron { \[dq]likes\[dq]: [ \[dq]code\[dq], \[dq]cheese\[dq], \[dq]meat\[dq] ] } \f[R] .fi .PP To preserve array keys, arrays are padded with \f[C]null\f[R] when values are missing: .IP .nf \f[C] $ gron testdata/two.json | grep likes | grep -v cheese json.likes = []; json.likes[0] = \[dq]code\[dq]; json.likes[2] = \[dq]meat\[dq]; \[u25B6] gron testdata/two.json | grep likes | grep -v cheese | gron --ungron { \[dq]likes\[dq]: [ \[dq]code\[dq], null, \[dq]meat\[dq] ] } \f[R] .fi .SH ADVANCED USAGE .PP Although gron\[cq]s primary purpose is API discovery, when combined with other tools like \f[C]grep\f[R] it can do some interesting things. .PP As an exercise, let\[cq]s try to mimic some of the examples from the jq tutorial (https://stedolan.github.io/jq/tutorial/). .RS .PP Disclaimer: munging data on the command line with gron can be useful, but using tools like \f[C]grep\f[R] and \f[C]sed\f[R] to manipulate the data is error-prone and shouldn\[cq]t be relied on in scripts. .RE .PP Get the last 5 commits from the gron repo: .IP .nf \f[C] $ gron \[dq]https://api.github.com/repos/tomnomnom/gron/commits?per_page=5\[dq] json = []; json[0] = {}; json[0].author = {}; json[0].author.avatar_url = \[dq]https://avatars.githubusercontent.com/u/58276?v=3\[dq]; json[0].author.events_url = \[dq]https://api.github.com/users/tomnomnom/events{/privacy}\[dq]; \&... json[4].parents[0].html_url = \[dq]https://github.com/tomnomnom/gron/commit/cbcad2299e55c28a9922776e58b2a0b5a0f05016\[dq]; json[4].parents[0].sha = \[dq]cbcad2299e55c28a9922776e58b2a0b5a0f05016\[dq]; json[4].parents[0].url = \[dq]https://api.github.com/repos/tomnomnom/gron/commits/cbcad2299e55c28a9922776e58b2a0b5a0f05016\[dq]; json[4].sha = \[dq]91b204972e63a1166c9d148fbbfd839f8697f91b\[dq]; json[4].url = \[dq]https://api.github.com/repos/tomnomnom/gron/commits/91b204972e63a1166c9d148fbbfd839f8697f91b\[dq]; \f[R] .fi .PP To make the rest of this a little more readable, let\[cq]s add an alias for that: .IP .nf \f[C] $ alias ggh=\[aq]gron \[dq]https://api.github.com/repos/tomnomnom/gron/commits?per_page=5\[dq]\[aq] \f[R] .fi .PP Extract just the first commit using \f[C]fgrep \[dq]json[0]\[dq]\f[R]: .IP .nf \f[C] $ ggh | fgrep \[dq]json[0]\[dq] json[0] = {}; json[0].author = {}; json[0].author.avatar_url = \[dq]https://avatars.githubusercontent.com/u/58276?v=3\[dq]; json[0].author.events_url = \[dq]https://api.github.com/users/tomnomnom/events{/privacy}\[dq]; json[0].author.followers_url = \[dq]https://api.github.com/users/tomnomnom/followers\[dq]; \&... json[0].parents[0].html_url = \[dq]https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311\[dq]; json[0].parents[0].sha = \[dq]48aba5325ece087ae24ab72684851cbe77ce8311\[dq]; json[0].parents[0].url = \[dq]https://api.github.com/repos/tomnomnom/gron/commits/48aba5325ece087ae24ab72684851cbe77ce8311\[dq]; json[0].sha = \[dq]7da81e29c27241c0a5c2e5d083ddebcfcc525908\[dq]; json[0].url = \[dq]https://api.github.com/repos/tomnomnom/gron/commits/7da81e29c27241c0a5c2e5d083ddebcfcc525908\[dq]; \f[R] .fi .PP Get just the committer\[cq]s name and the commit message using \f[C]egrep \[dq](committer.name|commit.message)\[dq]\f[R]: .IP .nf \f[C] $ ggh | fgrep \[dq]json[0]\[dq] | egrep \[dq](committer.name|commit.message)\[dq] json[0].commit.committer.name = \[dq]Tom Hudson\[dq]; json[0].commit.message = \[dq]Adds 0.1.7 to changelog\[dq]; \f[R] .fi .PP Turn the result back into JSON using \f[C]gron --ungron\f[R]: .IP .nf \f[C] \[u25B6] ggh | fgrep \[dq]json[0]\[dq] | egrep \[dq](committer.name|commit.message)\[dq] | gron --ungron [ { \[dq]commit\[dq]: { \[dq]committer\[dq]: { \[dq]name\[dq]: \[dq]Tom Hudson\[dq] }, \[dq]message\[dq]: \[dq]Adds 0.1.7 to changelog\[dq] } } ] \f[R] .fi .PP gron preserves the location of values in the JSON, but you can use \f[C]sed\f[R] to remove keys from the path: .IP .nf \f[C] $ ggh | fgrep \[dq]json[0]\[dq] | egrep \[dq](committer.name|commit.message)\[dq] | sed -r \[dq]s/(commit|committer)\[rs].//g\[dq] json[0].name = \[dq]Tom Hudson\[dq]; json[0].message = \[dq]Adds 0.1.7 to changelog\[dq] \f[R] .fi .PP With those keys removed, the result is a `flattened' object, which looks much cleaner when turned back into JSON with \f[C]gron --ungron\f[R]: .IP .nf \f[C] $ ggh | fgrep \[dq]json[0]\[dq] | egrep \[dq](committer.name|commit.message)\[dq] | sed -r \[dq]s/(commit|committer)\[rs].//g\[dq] | gron --ungron [ { \[dq]message\[dq]: \[dq]Adds 0.1.7 to changelog\[dq], \[dq]name\[dq]: \[dq]Tom Hudson\[dq] } ] \f[R] .fi .PP Removing the \f[C]fgrep \[dq]json[0]\[dq]\f[R] from the pipeline means we do the same for all commits: .IP .nf \f[C] $ ggh | egrep \[dq](committer.name|commit.message)\[dq] | sed -r \[dq]s/(commit|committer)\[rs].//g\[dq] | gron --ungron [ { \[dq]message\[dq]: \[dq]Adds 0.1.7 to changelog\[dq], \[dq]name\[dq]: \[dq]Tom Hudson\[dq] }, { \[dq]message\[dq]: \[dq]Refactors natural sort to actually work + be more readable\[dq], \[dq]name\[dq]: \[dq]Tom Hudson\[dq] }, \&... \f[R] .fi .PP To include the \f[C]html_url\f[R] key for each commit\[cq]s parents, all we need to do is add \f[C]parents.*html_url\f[R] into our call to \f[C]egrep\f[R]: .IP .nf \f[C] $ ggh | egrep \[dq](committer.name|commit.message|parents.*html_url)\[dq] | sed -r \[dq]s/(commit|committer)\[rs].//g\[dq] json[0].name = \[dq]Tom Hudson\[dq]; json[0].message = \[dq]Adds 0.1.7 to changelog\[dq]; json[0].parents[0].html_url = \[dq]https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311\[dq]; json[1].name = \[dq]Tom Hudson\[dq]; json[1].message = \[dq]Refactors natural sort to actually work + be more readable\[dq]; json[1].parents[0].html_url = \[dq]https://github.com/tomnomnom/gron/commit/3eca8bf5e07151f077cebf0d942c1fa8bc51e8f2\[dq]; \&... \f[R] .fi .PP To make the structure more like that in the final example in the \f[C]jq\f[R] tutorial, we can use \f[C]sed -r \[dq]s/\[rs].html_url//\[dq]\f[R] to remove the \f[C].html_url\f[R] part of the path: .IP .nf \f[C] \[u25B6] ggh | egrep \[dq](committer.name|commit.message|parents.*html_url)\[dq] | sed -r \[dq]s/(commit|committer)\[rs].//g\[dq] | sed -r \[dq]s/\[rs].html_url//\[dq] json[0].name = \[dq]Tom Hudson\[dq]; json[0].message = \[dq]Adds 0.1.7 to changelog\[dq]; json[0].parents[0] = \[dq]https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311\[dq]; json[1].name = \[dq]Tom Hudson\[dq]; json[1].message = \[dq]Refactors natural sort to actually work + be more readable\[dq]; json[1].parents[0] = \[dq]https://github.com/tomnomnom/gron/commit/3eca8bf5e07151f077cebf0d942c1fa8bc51e8f2\[dq]; \&... \f[R] .fi .PP And, of course, the statements can be turned back into JSON with \f[C]gron --ungron\f[R]: .IP .nf \f[C] $ ggh | egrep \[dq](committer.name|commit.message|parents.*html_url)\[dq] | sed -r \[dq]s/(commit|committer)\[rs].//g\[dq] | sed -r \[dq]s/\[rs].html_url//\[dq] | gron --ungron [ { \[dq]message\[dq]: \[dq]Adds 0.1.7 to changelog\[dq], \[dq]name\[dq]: \[dq]Tom Hudson\[dq], \[dq]parents\[dq]: [ \[dq]https://github.com/tomnomnom/gron/commit/48aba5325ece087ae24ab72684851cbe77ce8311\[dq] ] }, { \[dq]message\[dq]: \[dq]Refactors natural sort to actually work + be more readable\[dq], \[dq]name\[dq]: \[dq]Tom Hudson\[dq], \[dq]parents\[dq]: [ \[dq]https://github.com/tomnomnom/gron/commit/3eca8bf5e07151f077cebf0d942c1fa8bc51e8f2\[dq] ] }, \&... \f[R] .fi .SH EXAMPLES .PP Read from a local file/network: .IP .nf \f[C] $ gron /tmp/apiresponse.json $ gron http://jsonplaceholder.typicode.com/users/1 \f[R] .fi .PP Retrieve remote JSON and pipe through gron: .IP .nf \f[C] $ curl -s http://jsonplaceholder.typicode.com/users/1 | gron \f[R] .fi .PP Flatten and filter JSON through gron, before turning result back into JSON: .IP .nf \f[C] $ gron http://jsonplaceholder.typicode.com/users/1 | grep company | gron --ungron \f[R] .fi .SH TIPS .PP It\[cq]s recommended that you alias \f[C]ungron\f[R] or \f[C]norg\f[R] (or both!) to \f[C]gron --ungron\f[R]. Put something like this in your shell profile (e.g.\ in \f[C]\[ti]/.bashrc\f[R]): .IP .nf \f[C] alias norg=\[dq]gron --ungron\[dq] alias ungron=\[dq]gron --ungron\[dq] \f[R] .fi .PP Or you could create a shell script in your $PATH named \[ga]ungron\[ga] or \[ga]norg\[ga] to affect all users: \[ga]\[ga]\[ga] gron --ungron \[dq]$\[at]\[dq] \[ga]\[ga]\[ga] .SH EXIT STATUS .PP 0 OK .PD 0 .P .PD 1 Failed to open file .PD 0 .P .PD 2 Failed to read input .PD 0 .P .PD 3 Failed to form statements .PD 0 .P .PD 4 Failed to fetch URL .PD 0 .P .PD 5 Failed to parse statements .PD 0 .P .PD 6 Failed to encode JSON .SH REPORTING BUGS .PP Upstream bug tracker: https://github.com/tomnomnom/gron/issues .SH COPYRIGHT .PP Copyright (c) 2016 Tom Hudson .SH AUTHOR .PP This manual page is based on the gron documentation. It was created by Nick Morrott for the Debian GNU/Linux system, but may be used by others .SH SEE ALSO .PP \f[B]grep(1)\f[R], \f[B]ack(1)\f[R], \f[B]ag(1)\f[R], \f[B]rg(1)\f[R], \f[B]jq(1)\f[R]