How to turn JSON into CSV
The rules for flattening nested data into columns, the traps, and three ways to do it.
CSV holds rows and columns. JSON holds a tree. Turning one into the other always means a decision about what to do with the parts that do not fit. This page says what those decisions are, so you can choose them instead of discovering them later.
What converts cleanly
A list of flat objects:
[
{"id": 1, "name": "Ada", "active": true},
{"id": 2, "name": "Grace", "active": false}
]
becomes
id,name,active
1,Ada,true
2,Grace,false
One row for each object. One column for each key. That is the whole idea.
The four decisions
1. Which list?
CSV needs a list of records. A real API answer usually wraps it:
{
"total": 2,
"page": 1,
"results": [ { "id": 1 }, { "id": 2 } ]
}
The rows are in results. The tool must find it, and must tell you which list
it chose. This viewer uses the first list it finds and says so in a message,
so you can check.
2. What about nested objects?
{"user": {"name": "Ada", "city": "London"}}
The usual answer is to flatten with a dotted name:
user.name,user.city
Ada,London
This viewer does that, at any depth.
3. What about a list inside a record?
{"id": 1, "tags": ["a", "b"]}
There are three answers, and none is right for every case:
| Answer | Result | Good for |
|---|---|---|
| Keep the JSON text | "[""a"",""b""]" |
Keeping all the data |
| Join with a separator | a|b |
Reading by eye |
| One row for each item | two rows for id 1 |
Counting and grouping |
This viewer keeps the JSON text, because that loses nothing. If you need one
row for each item, do that step first with jq.
4. What if the records have different keys?
[{"a": 1}, {"b": 2}]
Every column must exist on every row, so the answer is a union of all the keys, with an empty cell where a record has none:
a,b
1,
,2
A tool that only reads the first record will silently drop the b column.
This viewer reads the first 500 records to build the columns, and says so when
the list is longer.
The escaping rules
These are the rules that break a spreadsheet when a tool gets them wrong.
- A cell that holds a comma, a quotation mark or a line break must be inside double quotation marks.
- A quotation mark inside such a cell is written twice.
- The line ending is
\r\nby convention (RFC 4180). Most programs accept\nas well.
a,b,c
"x,y","he said ""no""","one
two"
That is one row with three cells, and the third cell holds a line break.
Three ways to do it
In this viewer
Open the file, then Tools → To CSV. A window shows the result with Copy and Download. It flattens nested objects, fills missing cells and escapes correctly. Because the work happens in the browser, the file never leaves your machine, and a list of a million records still converts.
With jq
Full control, and good for a repeated job:
# pick the columns yourself
jq -r '["id","name"], (.results[] | [.id, .name]) | @csv' data.json > out.csv
# every key of the first record, as the header
jq -r '(.results[0] | keys_unsorted) as $cols
| $cols, (.results[] | [.[$cols[]]])
| @csv' data.json > out.csv
@csv handles the quotation marks for you. Use -r so the output is raw text.
In Python
import csv, json
with open('data.json') as f:
rows = json.load(f)['results']
columns = []
for row in rows:
for key in row:
if key not in columns:
columns.append(key)
with open('out.csv', 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=columns, extrasaction='ignore')
writer.writeheader()
writer.writerows(rows)
newline='' matters. Without it, Python writes an extra blank line between
rows on Windows.
Traps worth knowing
A spreadsheet changes your data. Excel turns 00123 into 123, and it
turns 1-2 into a date. This is not a fault in your CSV. If the identifiers
matter, import the file rather than opening it, and set the column type to
text.
A big number loses digits. An identifier with 18 digits goes through most tools as a floating point number and comes out changed. See big numbers in JSON. This viewer never turns the text into a number, so the digits survive the conversion.
UTF-8 and Excel. Older versions of Excel need a byte-order mark to read UTF-8 text. If the accents come out wrong, that is why.
null against empty. A null becomes an empty cell, and so does a key
that was missing. CSV cannot tell the two apart. If that difference matters,
CSV is the wrong format.
Try it on your own file. The tool runs in your browser. Nothing is uploaded, and nothing is stored on a server.
Open the JSON ViewerRead next
How to turn JSON into a TypeScript interface
Get types from a real API answer in seconds, and know what the generated types cannot tell you.
JSON Schema — check your data before it breaks something
Write a schema that catches bad data, with the keywords that matter and the traps that waste a day.