6 min read
How to Convert JSON to CSV in Python (3 Ways)
Python ships with everything you need: json for parsing and csv for writing. For flat data, the conversion is five lines. For nested objects, pandas handles the flattening. This guide covers both, plus the edge cases that silently produce wrong output.
The stdlib approach: json + csv
The simplest case is a JSON file containing an array of flat objects — every value is a string, number, boolean, or null, with no nesting. Python's built-in csv.DictWriter handles this directly.
[
{"id": 1, "name": "Ada Lovelace", "email": "[email protected]"},
{"id": 2, "name": "Grace Hopper", "email": "[email protected]"},
{"id": 3, "name": "Hedy Lamarr", "email": "[email protected]"}
]import csv
import json
with open("data.json") as f:
records = json.load(f)
with open("data.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)id,name,email
1,Ada Lovelace,[email protected]
2,Grace Hopper,[email protected]
3,Hedy Lamarr,[email protected]Three things to note. First, newline="" in the open call is required on Windows — without it, csv.writer adds extra blank lines between rows. Second, fieldnames=records[0].keys() takes columns from the first record only. If later records have extra keys, DictWriter raises ValueError by default. Third, json.load reads the entire file into memory, so this pattern works for files that fit in RAM — typically up to a few hundred megabytes.
Handling records with different keys
Real-world JSON is ragged. One record has a phone field, another doesn't. An API response adds a metadata key starting at record 50. If you take columns from records[0], every key that appears later is silently dropped.
The fix is to collect the union of all keys before writing:
import csv
import json
with open("data.json") as f:
records = json.load(f)
# Collect every key across all records — not just the first one
all_keys: list[str] = []
seen: set[str] = set()
for rec in records:
for key in rec:
if key not in seen:
all_keys.append(key)
seen.add(key)
with open("data.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=all_keys, extrasaction="ignore")
writer.writeheader()
writer.writerows(records)dict.fromkeys preserves insertion order while deduplicating — so columns appear in the order they are first encountered across all records. Missing values become empty cells automatically because DictWriter fills them with restval="" by default.
The pandas shortcut
If pandas is already in your stack, the conversion is two lines:
import pandas as pd
df = pd.read_json("data.json")
df.to_csv("data.csv", index=False)pd.read_json infers column types and handles ragged keys automatically. For flat data, this is the fastest way. The file goes through DataFrame, so you get filtering, sorting, and column selection for free before the export.
Nested JSON: use json_normalize
When records contain nested objects, csv.DictWriter writes Python's dict repr into the cell — {'city': 'London'} — which is useless in a spreadsheet. pd.json_normalize flattens nested objects into dot-notation columns like address.city and address.geo.lat.
import json
import pandas as pd
with open("orders.json") as f:
data = json.load(f)
df = pd.json_normalize(data, sep=".")
# Lists survive json_normalize untouched — join them or they
# appear as Python reprs like "['a', 'b']" in the CSV
for col in df.columns:
if df[col].apply(lambda v: isinstance(v, list)).any():
df[col] = df[col].apply(
lambda v: "; ".join(map(str, v)) if isinstance(v, list) else v
)
df.to_csv("orders.csv", index=False, encoding="utf-8-sig")The one catch everyone hits: json_normalize flattens objects but ignores lists. A tags field containing ["math", "computing"] passes through untouched and gets written as the Python repr. The snippet above handles this by joining lists with a semicolon. For a deeper look at array strategies, see the nested JSON to CSV guide.
Skip the code entirely
For one-off conversions, paste your JSON into the free converter — it handles nesting, arrays, and ragged keys in the browser with no upload and no size limit.
Open the JSON to CSV converterNDJSON (one object per line)
Log pipelines and streaming APIs often produce NDJSON (also called JSON Lines): one JSON object per line, no wrapping array. json.load fails on this format because the file is not valid JSON as a whole. Read it line by line instead:
import csv
import json
rows: list[dict] = []
with open("events.jsonl") as f:
for line in f:
line = line.strip()
if line:
rows.append(json.loads(line))
all_keys = list(dict.fromkeys(k for r in rows for k in r))
with open("events.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=all_keys)
writer.writeheader()
writer.writerows(rows)The pattern is the same as before — collect keys from all records, then write. The only difference is parsing each line separately.
One-liner for the terminal
When you need a quick conversion in a shell script or CI pipeline, a Python one-liner reads from stdin and writes to stdout:
python3 -c "
import csv, json, sys
data = json.load(sys.stdin)
keys = list(dict.fromkeys(k for r in data for k in r))
w = csv.DictWriter(sys.stdout, fieldnames=keys)
w.writeheader()
w.writerows(data)
" < data.json > data.csvThis uses the union-of-keys pattern so it works on ragged data. Pipe it into head or column -t -s, for a quick preview.
Common pitfalls
- Encoding. If the CSV will be opened in Excel, write with
encoding="utf-8-sig". The BOM tells Excel the file is UTF-8; without it, accented characters and CJK text display as garbage. - Line endings. Python's csv module writes
\r\non all platforms when you passnewline=""toopen. Omit that argument and you get double carriage returns on Windows. - Booleans and nulls.
json.loadmaps JSONtrue/falseto PythonTrue/False, andcsv.writerwrites them title-cased. If your downstream system expects lowercasetrue, convert explicitly before writing. - Large numbers. JSON has no integer size limit, but Excel truncates numbers beyond 15 significant digits. If you are exporting IDs like
9007199254740993, quote them as strings in the CSV or they will round on import. - Commas in values. Both
csv.writerandcsv.DictWriterhandle RFC 4180 quoting automatically — values containing commas, quotes, or newlines are wrapped in double quotes. Never build CSV by hand-joining with commas.
Which approach to use
Use the stdlib json + csv when you need zero dependencies and the data is flat or nearly flat. Use pandas when the data is nested, you want type inference, or you need to filter and reshape before exporting. Use the browser converter when you want the result in ten seconds without writing any code.
Going the other direction? The CSV to JSON converter reads the CSV back and rebuilds nested objects from dot-notation headers.