How to convert CSV to JSON in Python
csv.DictReader plus json.dump converts CSV to JSON in two lines, but every value comes out as a string. This script also types numbers and booleans, handles Excel's BOM and semicolons, and can rebuild nested objects.
1. The two-line version
import csv, json
json.dump(list(csv.DictReader(open('data.csv', newline='', encoding='utf-8-sig'))), open('data.json', 'w'), indent=2)
Each row becomes an object keyed by the header row, but "42" stays a string and empty cells become "". Use utf-8-sig, not utf-8: files saved by Excel start with a BOM, which would otherwise end up in the first key ("\ufeffid").
2. The full script
Save as csv2json.py:
import csv, json, re, sys
NUM = re.compile(r'-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?')
def typed(v):
# '42' -> 42, '3.5' -> 3.5, 'TRUE' -> True, '' -> None; ids with leading zeros ('02134') stay text
if v == '': return None
if NUM.fullmatch(v): return float(v) if any(c in v for c in '.eE') else int(v)
if v.lower() in ('true', 'false'): return v.lower() == 'true'
return v
def nest(row):
# {'address.city': 'X'} -> {'address': {'city': 'X'}}
out = {}
for key, value in row.items():
*path, last = key.split('.')
node = out
for part in path: node = node.setdefault(part, {})
node[last] = value
return out
with open(sys.argv[1], newline='', encoding='utf-8-sig') as f:
dialect = csv.Sniffer().sniff(f.read(4096), delimiters=',;\t|'); f.seek(0)
rows = [nest({k: typed(v) for k, v in r.items()}) for r in csv.DictReader(f, dialect=dialect)]
with open(sys.argv[2], 'w', encoding='utf-8') as f:
json.dump(rows, f, indent=2, ensure_ascii=False)
print(f'{len(rows)} rows -> {sys.argv[2]}')
3. Run it
curl -sL https://raw.githubusercontent.com/datasets/country-codes/main/data/country-codes.csv -o countries.csv
python3 csv2json.py countries.csv countries.json
Our run printed 249 rows -> countries.json. The start of the first object:
{"FIFA": "AFG", "Dial": 93, "ISO3166-1-Alpha-3": "AFG", "MARC": "af", "is_independent": "Yes", "ISO3166-1-numeric": 4, "GAUL": 1, ...
"Intermediate Region Code": null, ...}
4. What it handles
- Separator:
csv.Snifferdetects comma, semicolon (European Excel), tab or pipe from the first 4 KB. - Types: plain numbers become numbers,
TRUE/falsebecome booleans and empty cellsnull. Values with leading zeros (08001), thousands separators or currency stay text, so zip codes and ids are not mangled. - Nested keys: a header like
address.citybecomes{"address": {"city": ...}}. Our JSON to CSV script flattens JSON that way, and this script restores it: on the JSONPlaceholder users file, the only difference after the round trip was that coordinates stored as strings ("-37.3159") came back as numbers. - Unicode:
ensure_ascii=Falsewrites Zürich, notZ\u00fcrich.
5. Limits
- Types are guessed per cell, so one column can mix types: in the country file,
Dialis93for Afghanistan but"1-264"for Anguilla. If a consumer needs one type per column, convert those columns explicitly. - Numbered keys (
tags.0,tags.1) become an object with keys "0" and "1", not a list. The browser tool below turns them into arrays. - Decimal commas (
1,5) stay text. Replace them before typing if your file uses them. - The whole file is held in memory; for very large files write one JSON object per line (JSON Lines) inside the loop instead.
6. No code
Paste the cells from Excel, Google Sheets or a CSV file into the free Excel / CSV to JSON converter: same typing rules, optional nesting with arrays, and nothing is uploaded.
Frequently asked questions
How do I convert CSV to JSON in Python without pandas?
Use csv.DictReader to read rows as dictionaries and json.dump to write them, as in the two-line version above. The full script adds typing, separator detection and nesting.
Why are all my numbers strings?
The csv module never converts values; every cell is text. Convert them yourself, as the typed() function above does, or with pandas (pd.read_csv(...).to_json(orient='records')).
Why does my first key start with a strange character?
The file has a UTF-8 BOM (Excel adds one). Open it with encoding='utf-8-sig'.
Written with AI assistance. The script was run on 2026-10-01 with Python 3 standard library only, on the open country-codes dataset (datasets/country-codes on GitHub: 249 rows, 56 columns), on a semicolon-separated file with a BOM, accents and empty cells, and on the CSV produced by our JSON to CSV guide (nested JSON restored). PlainTables is our extension; the other methods are free and built into Excel, Google Sheets and pandas.