CSV to JSON converter (Python, standard library)
Convert any CSV file with a header row into a JSON array or JSON Lines. Handles quoted fields, a byte order mark, custom delimiters and malformed rows.
This script reads a CSV file and writes each row as a JSON object, using the header row for the keys. It uses only Python’s standard library, so it runs anywhere Python 3 is installed.
Usage
python3 csv_to_json.py people.csv # JSON array to the screen
python3 csv_to_json.py people.csv -o people.json # write to a file
python3 csv_to_json.py export.csv -d ';' # semicolon-separated
python3 csv_to_json.py data.tsv -d '\t' # tab-separated
python3 csv_to_json.py people.csv --lines # JSON Lines, one object per line
python3 csv_to_json.py people.csv --empty-as-null # empty cells become null
The script
#!/usr/bin/env python3
import argparse
import csv
import json
import sys
def main():
parser = argparse.ArgumentParser(description="Convert CSV to JSON.")
parser.add_argument("input", help="path to the CSV file")
parser.add_argument("-o", "--output", help="output file (default: print to stdout)")
parser.add_argument("-d", "--delimiter", default=",", help="field separator, e.g. ';' or '\\t'")
parser.add_argument("-e", "--encoding", default="utf-8-sig",
help="input encoding (default utf-8-sig also strips a BOM)")
parser.add_argument("--lines", action="store_true",
help="write JSON Lines (one object per line) instead of one array")
parser.add_argument("--empty-as-null", action="store_true",
help="turn empty cells into null instead of \"\"")
args = parser.parse_args()
delimiter = "\t" if args.delimiter in ("\\t", "tab") else args.delimiter
# newline="" lets the csv module handle line breaks inside quoted fields
with open(args.input, newline="", encoding=args.encoding) as f:
reader = csv.DictReader(f, delimiter=delimiter)
if reader.fieldnames is None:
sys.exit("error: the file is empty")
rows = []
for row in reader:
if None in row:
sys.exit(f"error: line {reader.line_num} has more fields than the header")
if args.empty_as_null:
row = {k: (None if v in ("", None) else v) for k, v in row.items()}
rows.append(row)
out = open(args.output, "w", encoding="utf-8") if args.output else sys.stdout
try:
if args.lines:
for row in rows:
out.write(json.dumps(row, ensure_ascii=False) + "\n")
else:
json.dump(rows, out, ensure_ascii=False, indent=2)
out.write("\n")
finally:
if out is not sys.stdout:
out.close()
print(f"converted {len(rows)} rows", file=sys.stderr)
if __name__ == "__main__":
main()
Example
Given this input, which has a quoted comma, doubled quotes and a line break inside a field:
id,name,note
1,Ada,"likes ""SQL"", tea"
2,Brian,"two
lines"
the output is:
[
{
"id": "1",
"name": "Ada",
"note": "likes \"SQL\", tea"
},
{
"id": "2",
"name": "Brian",
"note": "two\nlines"
}
]
How it works
csv.DictReaderparses quotes and embedded line breaks correctly. Opening the file withnewline=""is required for that.- The default encoding
utf-8-sigreads normal UTF-8 and also removes the byte order mark that some spreadsheet exports add. Without it, the first key could come out as"\ufeffid". Pass-e cp1252or another codec for legacy files. - If a row has more fields than the header,
DictReadercollects the extras under the keyNone. The script stops and reports the line number instead of silently writing broken data. Rows with fewer fields get empty values. ensure_ascii=Falsekeeps characters such asëreadable instead of writing\u00eb.
Limits and tweaks
Every value stays a string, because CSV has no types: "007" remains "007". If you need numbers, convert specific columns after reading, for example row["id"] = int(row["id"]), rather than guessing for every column.
The whole file is held in memory for array output. For very large files, use --lines; you can then change the loop to write each row as soon as it is read.
See also
- 0003 pathlib cheat sheet: files and folders in Python pathlib.Path is the modern way to build paths, read and write files, and list or search folders in Python. This page lists… Beginner · Reference · Any system Article Files & automation 15 Oct 2026
- 0004 Logging instead of print The logging module gives every message a level, a timestamp and a destination you can change without editing code. It is the… Intermediate · Concept · Any system Article Files & automation 15 Oct 2026
- 0009 CSV to JSON converter A small command-line tool that turns a CSV file into a JSON array of objects, with optional number detection, custom delimiters and… Beginner · How-to · Any system Script CSV & JSON · Files & automation 15 Oct 2026
- 0010 Find duplicate files by content Scans folders and lists files with identical content, using file size first and SHA-256 hashes second. It is read-only and only reports… Intermediate · How-to · Any system Script Files & automation 15 Oct 2026
- 0072 CSV vs JSON: choosing a format and avoiding the classic pitfalls CSV is a simple table of text; JSON holds nested, typed data. Both break in predictable ways: quoting, delimiters, encodings, numbers and… Beginner · Concept · Any system Article CSV & JSON 15 Oct 2026
- 0073 Fix: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) Python's json module found nothing it could parse at the very first character. The input is usually empty, HTML, or already consumed.… Beginner · Fix · Any system Article Common errors · CSV & JSON 15 Oct 2026
Repos in the wild
- Requests: HTTP for humans psf/requests Requests is the classic Python library for calling web APIs and downloading pages, with a simple API for GET and POST requests, headers, JSON and sessions. GitHub
- jq: command-line JSON processor jqlang/jq jq is a small command-line tool for reading, filtering and transforming JSON, like sed and awk for structured data. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +4,700 chars