7 min read
How to Convert JSON to CSV on the Command Line
Three tools cover 99% of JSON-to-CSV conversions in the terminal: jq for surgical extractions, mlr (Miller) for format-to-format streaming, and csvkit for CSV pipelines. No scripts, no Python imports — just pipes and flags.
jq: the Swiss-army knife
jq is a lightweight JSON processor available on most Linux and macOS systems. Install it with brew install jq, apt install jq, or download a static binary from the project page. The key operator for CSV output is @csv, which takes an array of scalars and produces a properly quoted RFC 4180 line.
Flat array of objects
Start with a JSON file containing an array of flat objects — every value is a string, number, or boolean:
[
{"id": 1, "name": "Ada Lovelace", "email": "ada@example.com"},
{"id": 2, "name": "Grace Hopper", "email": "grace@example.com"},
{"id": 3, "name": "Hedy Lamarr", "email": "hedy@example.com"}
]cat data.json | jq -r '
(.[0] | keys_unsorted) as $cols
| $cols, (.[] | [.[$cols[]]])
| @csv
'"id","name","email"
1,"Ada Lovelace","ada@example.com"
2,"Grace Hopper","grace@example.com"
3,"Hedy Lamarr","hedy@example.com"The -r flag tells jq to output raw strings instead of JSON-encoded ones (no extra surrounding quotes). The expression first extracts column names from the first object with keys_unsorted, then maps every object to an array of values in the same column order, and finally pipes each array through @csv.
Records with different keys
If records have ragged keys — one has phone, another doesn't — taking columns from the first object silently drops fields. Collect the union of all keys instead:
cat data.json | jq -r '
[.[] | keys[]] | unique as $cols
| $cols, (.[] | [.[$cols[]]])
| @csv
' > data.csvunique deduplicates and sorts alphabetically. Missing values become null, which @csv renders as an empty cell.
Nested objects
@csv only accepts flat arrays of scalars. If your JSON has nested objects, you must manually select and flatten the fields you need:
cat orders.json | jq -r '
.[] | [.id, .customer.name, .customer.email, (.items | length)]
| @csv
'This pulls .customer.name and .customer.email out of the nested object and counts items with length. There is no automatic dot-notation flattening in jq — you control exactly which fields appear and in what order. For deeper nesting strategies, see the nested JSON to CSV guide.
NDJSON (JSON Lines)
Log pipelines and APIs often produce NDJSON — one JSON object per line, no wrapping array. jq reads NDJSON natively (each line is a separate input), but @csv needs all records together to build a consistent header. The -s (slurp) flag collects every line into one array:
cat events.jsonl | jq -rs '
[.[] | keys[]] | unique as $cols
| $cols, ([inputs] | .[] | [.[$cols[]]])
| @csv
'If every line has the same keys and you know the column order, skip the header gymnastics:
cat events.jsonl | jq -r '[.id, .type, .timestamp] | @csv'Miller: format-aware streaming
mlr (Miller) is built for converting between structured data formats — JSON, CSV, TSV, and more — without loading the entire file into memory. Install with brew install miller or apt install miller.
The conversion is a single flag:
mlr --json2csv cat data.json > data.csvThat is it. Miller reads JSON, infers the schema from all records, and writes RFC 4180 CSV. It handles ragged keys automatically — missing fields become empty cells.
Nested objects with --flatsep
Unlike jq, Miller can flatten nested objects automatically using a separator:
mlr --json2csv --flatsep '.' cat orders.json > orders.csvA field like customer.name in the JSON becomes the CSV column header customer.name. Arrays are serialized as JSON strings — if you need to expand them into rows, jq or a script is a better fit.
Filtering and sorting inline
Miller supports a pipeline of verbs — filter, sort, cut, head, group-by — so you can transform the data during conversion:
mlr --json2csv filter '$status == "active"' then sort-by name data.jsonThis filters for active records and sorts by name, all in a single streaming pass.
NDJSON input
Use --jsonl2csv instead of --json2csv:
mlr --jsonl2csv cat events.jsonl > events.csvNo terminal? No problem.
Paste your JSON into the free online converter — it runs entirely in your browser, handles nesting and ragged keys, and has no file size limit. Nothing is uploaded to a server.
Open the JSON to CSV convertercsvkit: the CSV-first toolkit
csvkit is a Python-based suite of CSV utilities. Install with pip install csvkit. Its in2csv command converts JSON (and Excel, and fixed-width) to CSV:
in2csv data.json > data.csvWhere csvkit shines is chaining. Once the data is CSV, you can pipe it through the rest of the toolkit:
in2csv data.json | csvcut -c id,name,email | csvsort -c namecsvcut selects columns, csvsort sorts, csvlook pretty-prints a Markdown-style table, csvstat computes summary statistics, and csvgrep filters rows by pattern. The conversion is the entry point to a full analysis pipeline.
For nested JSON, in2csv flattens one level of nesting by default. Deeper structures are serialized as JSON strings. Use --no-inference to keep all values as strings and prevent csvkit from guessing column types:
in2csv --no-inference data.json | csvlookWhich tool to use
- jq — best when you need to cherry-pick specific fields from nested structures, or when jq is already in your workflow. Requires you to construct the column layout manually.
- Miller — best for straightforward format conversion, especially with large files. Streams records instead of loading everything into memory. Handles ragged keys and nested flattening without extra work.
- csvkit — best when the conversion is the first step in a longer CSV pipeline (filtering, stats, joins). Slower than jq and Miller on large files because it is Python-based.
All three produce RFC 4180-compliant CSV. If you need the output for Excel specifically, pipe through sed '1s/^/\xEF\xBB\xBF/' to prepend a UTF-8 BOM, or use the JSON to Excel converter which adds the BOM and CRLF line endings automatically.
Common pitfalls
- Missing header row. jq's
@csvdoes not add a header automatically — you must output column names as the first row yourself. Miller and csvkit include the header by default. - Null handling. jq renders
nullas an empty cell. Miller writes the literal stringnullunless you filter it withput. Check which behavior your downstream system expects. - Encoding. All three tools output UTF-8 by default. If the CSV will be opened in Excel on Windows, add a BOM or use
encoding="utf-8-sig"in your Python pipeline — otherwise non-ASCII characters display as garbage. - Array fields. None of these tools expand arrays into multiple rows automatically. jq's
lengthcan count them, Miller serializes them as JSON, and csvkit flattens one level. For full array expansion, see the nested JSON to CSV guide. - Column order. jq with
keys_unsortedpreserves JSON key order;keyssorts alphabetically. Miller preserves insertion order. Pick the variant that matches your expectations.
Need to go the other direction? The CSV to JSON converter handles the reverse. For programmatic approaches, see the guides for Python and JavaScript.