Skip to content
Commitpedia
Log in Join & edit

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.

Edit history of CSV to JSON converter (Python, standard library): 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
python
Licence
CC0-1.0

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.DictReader parses quotes and embedded line breaks correctly. Opening the file with newline="" is required for that.
  • The default encoding utf-8-sig reads 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 cp1252 or another codec for legacy files.
  • If a row has more fields than the header, DictReader collects the extras under the key None. The script stops and reports the line number instead of silently writing broken data. Rows with fewer fields get empty values.
  • ensure_ascii=False keeps 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.

  1. commitpedia-admin +4,700 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?