Flatten nested JSON into greppable, diffable, spreadsheet-friendly path = value lines. A small single-binary Rust tool in the spirit of gron, with a handful of extra output formats and a bidirectional --invert mode.
$ echo '{"users":[{"name":"alice","age":30},{"name":"bob","age":25}],"total":2}' | json-flatten -
users[0].name = "alice"
users[0].age = 30
users[1].name = "bob"
users[1].age = 25
total = 2
Pretty-printed JSON is hostile to the two things you actually do with JSON on the command line:
- Grep it. With pretty output you can't
grep emailbecause the line foremaildoesn't contain the object that owns it. With flat output every line is a full path from the root —grepjust works, and the result tells you where the match lives. - Diff it.
diff a.json b.jsonon pretty-printed JSON is noisy and order-sensitive.diff <(json-flatten --sort-keys a.json) <(json-flatten --sort-keys b.json)is small, stable, and reviewable.
A third handy case: loading JSON into a spreadsheet via --format tsv, or dumping a config into a shell env file via --format env.
# From source
cargo install --path .
# Or via Docker
docker build -t json-flatten .
docker run --rm -i json-flatten - < data.jsonMost people will want to alias it:
alias jf=json-flattenjson-flatten [OPTIONS] [FILE]
FILEis a JSON file, or-for stdin. Omit it to read stdin.
--format |
Example | Good for |
|---|---|---|
dots (default) |
users[0].name = "alice" |
grep, diff, reading |
jsonpath |
$.users[0].name = "alice" |
strict JSONPath consumers |
env |
USERS_0_NAME=alice |
shell env files |
tsv |
users[0].name\talice |
spreadsheets, awk |
json |
[{"path":"users[0].name","value":"alice","type":"string"}] |
feeding another JSON tool |
--no-color— disable ANSI color (auto-detected viaisattyandNO_COLOR).--path PATH— start at a sub-path, e.g.--path users[0].--depth N— stop recursion at N levels; the rest is emitted as a literal.--sort-keys— deterministic output.--include GLOB/--exclude GLOB— filter paths (may repeat).--invert/-u— readdotsoutput and rebuild the original JSON.
echo '{"a":{"b":{"c":1}}}' | json-flatten - | json-flatten - --invert
# {
# "a": { "b": { "c": 1 } }
# }flatten → invert is a true round-trip for everything flatten produces. That includes weird keys (with spaces, dots, or quotes) because the path quoting is chosen so the parser can unambiguously recover the original key.
An object key is rendered unquoted only when it's a "bare identifier" — starts with a letter or underscore, contains only letters, digits, and underscores. Anything else gets bracket-quoted with JSON-style string escapes:
foo.bar # bare
["a.b"] # key with a dot
["with \"quote\""] # key with a quote
["日本語"] # non-ASCII key
This is the same mechanism --invert uses to parse back, so round-tripping works for keys that would otherwise collide with the path syntax.
--format env is lossy by design. {"db.host": "a"} and {"db": {"host": "a"}} both produce DB_HOST=a. The uppercased-and-underscored key space is much smaller than the JSON key space. If you're going to re-read the JSON structure, use dots or json. If you just want a shell env file, env is fine — just don't round-trip it.
- No streaming. Input is read into memory and parsed in one shot. If you have 500 MB of JSON, use
jqwith-candsplit. - Numbers round-trip via serde_json::Number, which means scientific notation is preserved but integer vs float is distinguished. Infinity and NaN don't exist in JSON, so they can't appear.
- No JSONC or JSON5. Input must be strict JSON.
- Path quoting uses double quotes, not backticks. This lets the output survive being re-parsed as JSON-literal strings in other tools.
0— success1— bad JSON, path not found, invert failure2— bad arguments
MIT.