How to Flatten Nested JSON Arrays (and Export to CSV)
Nested JSON — arrays inside objects inside arrays — is everywhere: API responses, Stripe exports, MongoDB dumps, log files. Spreadsheets can't read it, and most "JSON to CSV" tools either choke on nesting or silently turn your data into [object Object]. This guide shows how to flatten nested JSON into flat rows from the command line, for free, without uploading your data anywhere.
The problem with one-level flattening
Take a typical export:
[
{ "id": 1, "name": "Widget", "tags": ["sale", "new"] },
{ "id": 2, "name": "Gadget", "tags": ["sale"] }
]
Each item has a tags array. A naive converter gives you a column containing the raw array as text. What you usually want is one row per tag, with the parent fields repeated:
$ transmute items.json -p '[{"op":"flatten","field":"tags"}]' -o csv id,name,tags 1,Widget,sale 1,Widget,new 2,Gadget,sale
The flatten operation expands an array field into multiple rows. Non-array fields are copied onto every expanded row, so each row stays self-contained.
Flattening nested objects instead of arrays
If the nested value is an object rather than an array — say a meta object with several keys — you have two options:
Do not leave it nested
CSV has no way to hold an object. If a row still contains one when it is serialized, the cell comes out as [object Object], and an array comes out as its elements joined with commas. So promote the fields you need before the CSV step:
Promote fields with map
$ transmute items.json -p '[{"op":"map","expr":"({...item, color: item.meta?.color ?? null})"}]' -o csv
The spread keeps existing columns and adds the promoted field on top.
Adding computed columns while you're at it
The add operation evaluates an expression per row — useful for counts, totals or derived flags:
$ transmute items.json -p '[{"op":"add","fields":{"tag_count":"item.tags.length"}}]' -o csv id,name,tags,tag_count 1,Widget,"sale,new",2 2,Gadget,sale,1
Deeply nested structures
For two or more levels of nesting, chain operations. Flatten level by level:
$ transmute orders.json -p '[{"op":"flatten","field":"lines"},{"op":"flatten","field":"discounts"}]' -o csv
If a field is missing on some rows, flatten passes those rows through untouched — you don't lose records, they just aren't expanded.
Getting the CLI
| Method | Command |
|---|---|
| npx (no install) | npx @mahope/transmute items.json -o csv |
| npm (global) | npm i -g @mahope/transmute |
All processing happens locally. Nothing is uploaded.
Gotchas
- Arrays of objects: when the flattened values are objects, their keys are merged into the row. Name collisions keep the parent's value unless the child defines the key.
- null vs missing: both serialize to empty CSV cells; use
mapif you need to distinguish them. - Excel and leading zeros: if IDs like
"007"lose their zeros after import, format the column as text in your spreadsheet tool. - Huge files: everything is held in memory, so for multi-hundred-MB files split the input first or take a slice with
{"op":"head","n":50000}.