Convert JSON to CSV with Python using the columns you need

Turn a small JSON export into a CSV with explicit columns, preserve missing values, and catch nested data before writing the file.

Beginner6 min read
In this guide

You have an export that opens as a wall of brackets, but you need a table. Python can turn it into a CSV without an online converter—and your data can stay on your computer.

The important decision is which fields should become columns. This guide makes that choice explicit. It converts a small list of records, keeps your original file, and stops if a selected field contains another object or list instead of one cell.

Choose your input

You need Python 3.12 or newer, a plain-text editor, and a terminal. On Windows, open PowerShell and run py --version; on macOS or Linux, open Terminal and run python3 --version. If Python is missing, install it from Python’s downloads page, reopen the terminal, and try again. No extra packages or VPS are needed.

Create a folder named json-table. Copy your JSON export into it as orders.json, or save this fictional example as a UTF-8 plain-text file:

[
  {"order_id": "1001", "item": "Notebook, blue", "quantity": 2},
  {"order_id": "1002", "item": "Mug", "quantity": 1},
  {"order_id": "1003", "item": "Pencil", "quantity": null}
]

The outer [ and ] mean this is a list. Each {...} is one record, and names such as item are its fields. Use a small trusted export that fits comfortably in memory. This script reads the whole input at once.

If your export instead looks like {"results": [...]}, it has a wrapper. Obtain an export of the list itself; this particular script does not guess which nested list you meant. It also does not process JSON Lines, where each line is a separate JSON document.

Save the converter

Save the following as json_to_csv.py in the same folder. In Notepad, choose All files when saving so that the name does not become json_to_csv.py.txt.

import argparse
import csv
import json
from decimal import Decimal
from pathlib import Path

parser = argparse.ArgumentParser(description="Convert a list of JSON records.")
parser.add_argument("file")
parser.add_argument("--columns", nargs="+", required=True)
parser.add_argument("--output", required=True)
args = parser.parse_args()

try:
    if len(args.columns) != len(set(args.columns)):
        raise ValueError("Choose each column only once.")
    if Path(args.file).resolve() == Path(args.output).resolve():
        raise ValueError("Input and output must be different files.")
    with open(args.file, encoding="utf-8-sig") as source:
        records = json.load(source, parse_float=Decimal)
    if not isinstance(records, list):
        raise ValueError("Expected a list of records, starting with [.")
    rows = []
    missing = 0
    for number, record in enumerate(records, 1):
        if not isinstance(record, dict):
            raise ValueError(f"Record {number} is not an object.")
        row = []
        for column in args.columns:
            value = record.get(column)
            if isinstance(value, (dict, list)):
                raise ValueError(f"Record {number}: {column} contains nested data.")
            if value is None:
                missing += 1
                value = ""
            elif isinstance(value, bool):
                value = "true" if value else "false"
            row.append(value)
        rows.append(row)
    # Exclusive creation prevents overwriting an earlier result.
    with open(args.output, "x", encoding="utf-8", newline="") as target:
        writer = csv.writer(target)
        writer.writerow(args.columns)
        writer.writerows(rows)
    print(f"Saved {len(rows)} records; {missing} missing/null cells left blank.")
except (OSError, ValueError, csv.Error) as error:
    parser.exit(1, f"Stopped: {error}\n")

This keeps only the fields you request, in your chosen order. A missing field and a JSON null both become a blank cell; that distinction will not survive in the CSV. Nested fields you did not select are ignored.

Run it with your column names

Open a terminal in json-table: in Windows File Explorer, open the folder, type powershell in the address bar, and press Enter. On macOS/Linux, type cd , drag the folder into Terminal, then press Enter.

In Windows PowerShell:

py json_to_csv.py orders.json --columns order_id item quantity --output orders-table.csv

On macOS/Linux, use the same arguments with python3:

python3 json_to_csv.py orders.json --columns order_id item quantity --output orders-table.csv

For your own export, replace the three names after --columns with its exact field names. Names containing spaces need quotation marks, for example --columns "Order ID" "Item name". Capitalization matters.

With the example, the message should be:

Saved 3 records; 1 missing/null cells left blank.

Check the table before importing it

Open orders-table.csv in a text editor first. It should contain:

order_id,item,quantity
1001,"Notebook, blue",2
1002,Mug,1
1003,Pencil,

The quotes keep the comma in Notebook, blue inside one cell. Check the first record, the last record, and the blank quantity before opening the file in a spreadsheet or importing it elsewhere.

For your own export, compare the reported record count with the source list or exporting application’s count. Check several selected values against their original records, including decimal numbers and any missing fields. Decimal values are read without converting them to binary floating-point numbers first, avoiding that source of rounding. A spreadsheet can still round them when it imports the CSV; use text columns when exact digits matter.

For identifiers with leading zeroes, import the column as text in your spreadsheet. Also use text import for untrusted values that begin with formula characters such as =; spreadsheet software may otherwise interpret them as formulas. The converter preserves values—it does not make untrusted content safe or choose spreadsheet types for you.

If it stops or the result is wrong

  • Nested data: choose a scalar field instead, such as an ID or name. Flattening a customer’s list of purchases requires a separate rule about how many rows to create.
  • Every cell in a column is blank: check the field spelling and capitalization. Missing fields are deliberately blank, so a misspelled column name can still produce a file.
  • JSON error: check that the input is a complete JSON document, not a copied API error page or a file with a missing bracket. Keep the original and obtain a fresh export.
  • File already exists: choose a new output name. The script will not replace the old result.
  • No records: an empty list produces only the header. That is valid conversion, but not a useful export if you expected orders.

The program finishes after one run; there is nothing to disable. Your original JSON stays in place. If you need to validate a CSV you received from another source, use the CSV reading guide.

← Back to automation guides