How to convert JSON to CSV in Python
csv.DictWriter does the writing; the work is flattening nested objects and collecting every key, because API records rarely all have the same fields. This script does both with no dependencies.
1. The script
Save as json2csv.py:
import csv, json, sys
def flatten(obj, prefix=''):
# {"a": {"b": 1}, "tags": ["x", "y"]} -> {"a.b": 1, "tags.0": "x", "tags.1": "y"}
out = {}
items = obj.items() if isinstance(obj, dict) else enumerate(obj)
for key, value in items:
name = f'{prefix}{key}'
if isinstance(value, (dict, list)) and value:
out.update(flatten(value, name + '.'))
else:
out[name] = '' if value is None or value in ({}, []) else value
return out
data = json.load(open(sys.argv[1], encoding='utf-8'))
if isinstance(data, dict): # a single object, or {"results": [...]}
lists = [v for v in data.values() if isinstance(v, list)]
data = lists[0] if len(lists) == 1 else [data]
rows = [flatten(r) if isinstance(r, (dict, list)) else {'value': r} for r in data]
columns = list(dict.fromkeys(k for r in rows for k in r)) # union of keys, first-seen order
with open(sys.argv[2], 'w', newline='', encoding='utf-8-sig') as f:
w = csv.DictWriter(f, fieldnames=columns)
w.writeheader()
w.writerows(rows)
print(f'{len(rows)} rows, {len(columns)} columns -> {sys.argv[2]}')
2. Run it
curl -s https://jsonplaceholder.typicode.com/users -o users.json
python3 json2csv.py users.json users.csv
Our run printed 10 rows, 15 columns -> users.csv. The nested address and company objects become their own columns:
id,name,username,email,address.street,address.suite,address.city,address.zipcode,address.geo.lat,address.geo.lng,phone,website,company.name,company.catchPhrase,company.bs
1,Leanne Graham,Bret,Sincere@april.biz,Kulas Light,Apt. 556,Gwenborough,92998-3874,-37.3159,81.1496,1-770-736-8031 x56442,hildegard.org,Romaguera-Crona,Multi-layered client-server neural-net,harness real-time e-markets
3. What it handles
- Missing keys: the header is the union of every record's keys, so a field that only some records have gets its own column and is left empty elsewhere. (Plain
DictWriter(fieldnames=rows[0].keys())raisesValueError: dict contains fields not in fieldnameson such data.) - Wrapped responses: a top-level object holding exactly one list, like
{"results": [...]}, is unwrapped. Any other object becomes one row. - Commas, quotes and newlines inside values are quoted by the
csvmodule:he said "hi", okis written as"he said ""hi"", ok". - Excel: the file is written as UTF-8 with a BOM (
utf-8-sig), so accented and non-Latin text opens correctly in Excel. Useencoding='utf-8'if another program chokes on the BOM.
4. Limits
- Lists become numbered columns (
tags.0,tags.1). A record with long lists can add many columns; the GitHub issues API gave 90. To keep a list in one cell instead, replace the list branch without[name] = json.dumps(value). true/falseare written asTrue/False, andnullas an empty cell.- The whole file is loaded into memory. For multi-gigabyte files or JSON Lines (one object per line), read line by line with
json.loadsinstead.
5. No code
To convert a pasted JSON array without Python, use the free JSON to CSV tool; it runs in your browser and the data is not uploaded. For tables on web pages, PlainTables exports them to CSV or Excel in one click.
Frequently asked questions
Can I do this without pandas?
Yes. The script above uses only json and csv from the standard library. With pandas, pd.json_normalize(data).to_csv('out.csv', index=False) does a similar flattening.
Why do I get 'dict contains fields not in fieldnames'?
Your records do not all have the same keys, and fieldnames was built from the first record only. Build it from the union of all keys, as the script does.
Written with AI assistance. The script was run on 2026-10-01 with Python 3 standard library only, on the JSONPlaceholder /users API (10 rows, nested address and company objects → 15 columns), the GitHub issues API for python/cpython (5 issues → 90 columns, every row the same width) and a hand-made file with a 'results' wrapper object, nulls, lists and quotes. PlainTables is our extension; the other methods are free and built into Excel, Google Sheets and pandas.