Skip to content
JSON to CSV

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:

data.json
[
  {"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"}
]
Shell
cat data.json | jq -r '
  (.[0] | keys_unsorted) as $cols
  | $cols, (.[] | [.[$cols[]]])
  | @csv
'
Output
"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:

Shell
cat data.json | jq -r '
  [.[] | keys[]] | unique as $cols
  | $cols, (.[] | [.[$cols[]]])
  | @csv
' > data.csv

unique 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:

Shell
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:

Shell
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:

Shell
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:

Shell
mlr --json2csv cat data.json > data.csv

That 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:

Shell
mlr --json2csv --flatsep '.' cat orders.json > orders.csv

A 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:

Shell
mlr --json2csv filter '$status == "active"' then sort-by name data.json

This filters for active records and sorts by name, all in a single streaming pass.

NDJSON input

Use --jsonl2csv instead of --json2csv:

Shell
mlr --jsonl2csv cat events.jsonl > events.csv

No 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 converter

csvkit: 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:

Shell
in2csv data.json > data.csv

Where csvkit shines is chaining. Once the data is CSV, you can pipe it through the rest of the toolkit:

Shell
in2csv data.json | csvcut -c id,name,email | csvsort -c name

csvcut 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:

Shell
in2csv --no-inference data.json | csvlook

Which 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 @csv does 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 null as an empty cell. Miller writes the literal string null unless you filter it with put. 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 length can 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_unsorted preserves JSON key order; keys sorts 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.

Command-line JSON to CSV: common questions

Which tool is best for converting JSON to CSV on the command line?
For quick one-off conversions of flat arrays, jq is the most common choice — it is pre-installed on many systems and needs no setup. For complex transformations or mixed formats, Miller (mlr) is faster and handles format conversion natively. csvkit is best when you need to chain CSV operations like filtering, sorting, and pretty-printing.
Can jq handle nested JSON objects?
jq does not flatten nested objects automatically — @csv only works on flat arrays of scalars. You need to manually select and flatten fields in the jq expression, for example: [.id, .address.city, .address.zip] | @csv. For automatic dot-notation flattening, use Miller with the --flatsep flag instead.
How do I convert NDJSON (JSON Lines) to CSV?
With jq, use the -s (slurp) flag to read all lines into a single array, then apply the normal @csv pattern. With Miller, use --jsonl2csv instead of --json2csv. With csvkit, pipe through in2csv which auto-detects the format. If every line has the same keys, the simplest jq approach is: jq -r '[.field1, .field2] | @csv' file.jsonl.
How do I handle JSON files too large for memory?
jq with the --stream flag can process large files incrementally, but the syntax is complex. Miller is a better choice for large files — it streams records by default and never loads the whole file into memory. For truly massive files, convert to NDJSON first, then process line by line.
Why does jq wrap every field in quotes?
The @csv operator in jq follows RFC 4180 strictly — strings are always double-quoted, and quotes inside strings are escaped by doubling them. Numbers and booleans are not quoted. This is correct CSV behavior; if your downstream tool chokes on quoted fields, the problem is in the parser, not in jq's output.
Can I convert CSV back to JSON on the command line?
Yes. With Miller: mlr --csv2json cat data.csv. With jq, you would need to parse CSV manually which is painful — jq is not designed for CSV input. csvkit does not convert CSV to JSON directly, but you can combine it with other tools. Or use the free CSV to JSON converter on this site for a zero-setup option.