Back to Articles

gron: How Flattening JSON Makes It Greppable (And Why You'd Want To)

[ View on GitHub ]

gron: How Flattening JSON Makes It Greppable (And Why You'd Want To)

Hook

You can master gron's entire feature set in under five minutes, yet it remains one of the most-starred JSON tools on GitHub. That ratio of simplicity to utility is almost unheard of in developer tooling.

Context

JSON has won the API wars. Whether you're debugging microservices, exploring third-party APIs, or analyzing log files, you're swimming in JSON. The problem? JSON's nested structure fights against Unix philosophy. You can't grep for a specific field without complex regex gymnastics because the structure spans multiple lines. Tools like jq solve this brilliantly with a powerful query language, but they come with a steep learning curve. You need to remember syntax for object indexing, array iteration, and pipe operations. For quick exploration tasks—the "I just need to find all the email addresses in this response" moments—it feels like overkill.

This is where gron enters. Created by Tom Hudson (tomnomnom), a security researcher who regularly spelunks through API responses hunting for vulnerabilities, gron takes a radically different approach. Instead of teaching you a new query language, it transforms JSON into a format that already works with the tools you know: grep, sed, awk, and every other line-oriented Unix utility. It flattens nested structures into discrete assignment statements, one per line, each showing the complete path to a value. Suddenly, grep email just works. More importantly, gron can reverse the transformation, so you can grep, filter, modify, and reconstruct valid JSON—a edit-transform-restore workflow using only standard Unix tools.

Technical Insight

parsed tree

path + value

--ungron flag

parsed assignments

grep filter

JSON Input

file/URL/stdin

JSON Parser

Recursive Tree

Traverser

Statement Formatter

path = value

Flattened Output

grep-able lines

Gron Statements

stdin/file

Assignment Parser

JSON Reconstructor

JSON Output

System architecture — auto-generated

At its core, gron is a recursive JSON traverser that outputs assignment statements. When you run gron data.json, it walks the JSON tree depth-first, building up path strings as it descends. Each leaf value becomes a line like json.users[0].email = "alice@example.com";. The output is syntactically valid JavaScript, which is a clever design choice—it's both human-readable and executable in a JS runtime if needed.

Here's a practical example. Say you're working with this JSON response from an API:

{
  "users": [
    {"name": "Alice", "email": "alice@example.com", "role": "admin"},
    {"name": "Bob", "email": "bob@example.com", "role": "user"}
  ],
  "metadata": {
    "total": 2,
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Run it through gron:

$ gron users.json
json = {};
json.metadata = {};
json.metadata.timestamp = "2024-01-15T10:30:00Z";
json.metadata.total = 2;
json.users = [];
json.users[0] = {};
json.users[0].email = "alice@example.com";
json.users[0].name = "Alice";
json.users[0].role = "admin";
json.users[1] = {};
json.users[1].email = "bob@example.com";
json.users[1].name = "Bob";
json.users[1].role = "user";

Now you can use grep naturally: gron users.json | grep email instantly shows all email-related paths. But the real power emerges with the --ungron flag, which reverses the process. This enables filter-reconstruct workflows:

# Extract only admin users
$ gron users.json | grep '"admin"' | gron --ungron
{
  "users": [
    {
      "role": "admin"
    }
  ]
}

# Remove all email fields
$ gron users.json | grep -v email | gron --ungron

Architecturally, gron uses Go's encoding/json package to parse input into an abstract syntax tree, then implements custom traversal logic. The key data structure is a path accumulator—a slice of strings representing the current position in the JSON tree. As it recurses into objects, it appends keys; for arrays, it appends index notation. When it hits a primitive value, it outputs the accumulated path with the value.

The ungron operation is more interesting. It parses the assignment syntax (which looks like JavaScript but is actually a simplified grammar), extracts paths and values, then reconstructs the JSON tree. This requires careful handling of type inference—when it sees json.users[0], it knows to create an array; when it sees json.users[0].name, it knows that array element is an object. The parser is deliberately permissive, accepting the output format it generates plus some variations.

One elegant detail: gron outputs assignments in a deterministic order (depth-first, lexicographic within each level). This makes diffs meaningful. You can gron two JSON files, diff the results, and actually understand what changed—something that's painful with raw JSON where key ordering is technically insignificant but visually confusing.

The tool also supports reading from URLs directly (gron https://api.example.com/users), which is incredibly handy for API exploration. It uses standard HTTP libraries, follows redirects, and respects timeouts. For local development, piping works as expected: curl api.example.com | gron integrates seamlessly into existing workflows.

Gotcha

The biggest gotcha is array handling after filtering. When you filter out array elements, gron preserves their original indices by inserting nulls. For example, if you grep for just the second user in an array, you get json.users[1] = {...}, and when you ungron, it creates [null, {...}] with a null in position zero. This maintains index accuracy but produces unexpected nulls in your JSON. There's no built-in flag to collapse arrays—you'd need post-processing or accept the nulls. This design choice prioritizes correctness (preserving which element was which) over convenience, which makes sense for debugging but frustrates data extraction workflows.

The other limitation is expressiveness. gron is intentionally simple—it does exactly one thing. You can't compute values, aggregate data, or perform conditional transformations. If you need to sum all prices, find the maximum value, or restructure nested data significantly, you'll hit walls quickly. In these cases, the "grep and pray" approach forces you to chain multiple gron/grep cycles or give up and switch to jq or a programming language. Also, extremely large JSON files (hundreds of MB or larger) become unwieldy since gron outputs one line per leaf value. Grepping through millions of lines works but isn't elegant, and you lose gron's simplicity advantage when you're scrolling through pages of assignments.

Verdict

Use if: You regularly explore unfamiliar APIs, deal with deeply nested JSON where you're hunting for specific fields, or simply prefer Unix text tools over learning domain-specific query languages. It's perfect for security research, incident debugging, or quick data extraction tasks where you know roughly what you're looking for but not the exact path. Also use it if you work in constrained environments where installing jq or Node.js is bureaucratic—gron is a single binary with zero runtime dependencies. Skip if: You're already fluent in jq and your tasks involve complex transformations, aggregations, or computed fields. Also skip it for production data pipelines where you need robust error handling and type safety—gron is a reconnaissance tool, not a data processing framework. Finally, skip it if you're working with well-documented APIs where you know the structure upfront; in those cases, writing a direct jq query is faster than exploring with gron.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/tomnomnom-gron.svg)](https://starlog.is/api/badge-click/developer-tools/tomnomnom-gron)