CSV to JSON in Python

json.dumps(list(csv.DictReader(f)), indent=2)

json.dumps(list(csv.DictReader(f)), indent=2) turns an open CSV file into a JSON array with one object per row, keyed by the header row. csv.DictReader does the CSV parsing, quoted commas and doubled quotes included, so there’s no reason to split lines on , yourself.

import csv, json

with open("people.csv", newline="", encoding="utf-8-sig") as f:
    rows = list(csv.DictReader(f))

print(json.dumps(rows, indent=2, ensure_ascii=False))

For this people.csv:

id,name,city,zip
1,"Lovelace, Ada",London,02134
2,Kurt Gödel,Wien,1090
3,"Alan ""Al"" Turing",,

it prints (Python 3.14):

[
  {
    "id": "1",
    "name": "Lovelace, Ada",
    "city": "London",
    "zip": "02134"
  },
  {
    "id": "2",
    "name": "Kurt Gödel",
    "city": "Wien",
    "zip": "1090"
  },
  {
    "id": "3",
    "name": "Alan \"Al\" Turing",
    "city": "",
    "zip": ""
  }
]

The converter below prints exactly the same, with its typing box unticked.

Everything stays a string

DictReader never guesses types: id is "1", and an empty cell is "", not None. That’s the safe default for a column like zip, where 02134 as a number would lose its zero. Convert the columns you know:

rows = [{**row, "id": int(row["id"])} for row in rows]
# [{'id': 1, 'name': 'Lovelace, Ada', 'city': 'London', 'zip': '02134'}, ...]

Tick Read 123, 1.5, true, false and empty cells as numbers, booleans and null in the tool and it converts only the cells that write back the way they’re spelled: 1 and 1090 become numbers, 02134 stays a string, and the empty cells become null.

The three arguments that matter

  • newline="" leaves line endings to the csv module, as its documentation asks for every file you hand it. Without it, Python’s newline translation turns a \r\n inside a quoted field into \n before the parser sees it, so a multi-line cell from Excel comes back changed.
  • encoding="utf-8-sig" drops the byte order mark that Excel’s “CSV UTF-8” format writes first. With plain utf-8 it stays in the first column name, {"id": "1", ...}, and row["id"] raises KeyError. With no encoding at all, open uses the locale’s, which on Windows is usually not UTF-8.
  • ensure_ascii=False writes Gödel as it is. The default escapes every non-ASCII character, "Kurt Gödel": still valid JSON, but harder to read.

Ragged rows

A row with more fields than the header puts the extras in a list under the key None, and json.dumps writes that key as "null". A short row’s missing fields are None. For id,name, then 1,Ada,extra, then 2:

[{"id": "1", "name": "Ada", "null": ["extra"]}, {"id": "2", "name": null}]

restkey="extra" names the overflow, and json.dumps(..., sort_keys=True) fails with a TypeError while a None key is there. The tool calls the extra column column3, gives the short row null for the fields it lacks, as DictReader does, and warns about both rows.

Big files and the shell

For a file you don’t want in memory at once, write JSON Lines, one object per line:

with open("people.csv", newline="", encoding="utf-8-sig") as f, open("people.jsonl", "w", encoding="utf-8") as out:
    for row in csv.DictReader(f):
        out.write(json.dumps(row, ensure_ascii=False) + "\n")

From a terminal, the same conversion is one line:

python3 -c 'import csv,json,sys; print(json.dumps(list(csv.DictReader(open(sys.argv[1], newline="", encoding="utf-8-sig"))), indent=2, ensure_ascii=False))' people.csv

For a semicolon file, pass delimiter=";" to DictReader.

Try it in the CSV ↔ JSON Converter

JSON

3 rows × 4 columns · delimiter comma · everything runs in this page, nothing is uploaded.

Preview · 4 rows
#idnamecityzip
21Lovelace, AdaLondon02134
32Kurt GödelWien1090
43Alan "Al" Turing

Runs in your browser. Nothing you enter is sent anywhere.