How to Convert JSON to CSV in Python: 3 Methods with Examples (2026)

JSON and CSV are the two most common data formats in software development. Converting between them is a daily task for data engineers, analysts, and backend developers. This guide covers three battle-tested Python methods — from the standard library to pandas — with real examples, edge cases, and production-ready patterns.

3

methods covered in this guide

pandas

best for complex/nested data

csv module

best for simple flat data

0 deps

for the standard library approach

1

When to Use Each Method

ItemUse csv + json modulesUse pandas
DependenciesNone (standard library)pip install pandas
Data structureSimple flat JSON arraysNested, complex, or large data
Performance (large files)Use csv.DictWriter for streamingOptimized with chunking support
Column controlManual with fieldnames paramAutomatic column detection
Nested objectsRequires manual flatteningjson_normalize() handles it
Data cleaningManual string operationsBuilt-in data manipulation
Learning curveLowMedium
2

Method 1: csv and json Standard Library

The simplest approach for flat JSON arrays. No external dependencies required. This works when your JSON is an array of objects where every object has the same keys.

pythonjson_to_csv_basic.py
import json
import csv

# Load JSON data (from file)
with open('data.json', 'r') as f:
    data = json.load(f)

# data looks like:
# [
#   {"name": "Alice", "age": 30, "city": "New York"},
#   {"name": "Bob", "age": 25, "city": "London"}
# ]

# Write to CSV
with open('output.csv', 'w', newline='', encoding='utf-8') as f:
    if not data:
        print("No data to write")
    else:
        # Get column names from first record
        fieldnames = list(data[0].keys())
        writer = csv.DictWriter(f, fieldnames=fieldnames)

        writer.writeheader()       # Write the header row
        writer.writerows(data)     # Write all data rows

print(f"Wrote {len(data)} rows to output.csv")

Always use newline='' when writing CSV

On Windows, Python's csv module requires you to open the file with newline='' to prevent extra blank lines between rows. This is a common gotcha.

If your JSON is a string (not already loaded), you can convert in memory without writing to disk:

pythonjson_string_to_csv.py
import json
import csv
import io

def json_to_csv_string(json_string: str) -> str:
    """Convert a JSON string to a CSV string."""
    data = json.loads(json_string)

    if not data or not isinstance(data, list):
        raise ValueError("Input must be a JSON array of objects")

    output = io.StringIO()
    fieldnames = list(data[0].keys())
    writer = csv.DictWriter(output, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(data)

    return output.getvalue()

# Usage
json_data = '[{"name":"Alice","score":95},{"name":"Bob","score":87}]'
csv_output = json_to_csv_string(json_data)
print(csv_output)
# name,score
# Alice,95
# Bob,87
3

Method 2: pandas json_normalize for Nested JSON

When your JSON has nested objects or arrays inside objects, the standard library requires manual flattening. pandas json_normalize() handles this automatically.

pythonnested_json_to_csv.py
import pandas as pd
import json

# Nested JSON example:
# [
#   {
#     "name": "Alice",
#     "address": {"city": "New York", "country": "US"},
#     "scores": [95, 87, 92]
#   }
# ]

with open('nested_data.json', 'r') as f:
    data = json.load(f)

# json_normalize flattens nested objects using dot notation
df = pd.json_normalize(data)
# Creates columns: name, address.city, address.country, scores

# For custom separator between parent and child keys:
df = pd.json_normalize(data, sep='_')
# Creates columns: name, address_city, address_country

# Save to CSV
df.to_csv('output.csv', index=False)
print(df.head())
print(f"Shape: {df.shape}")  # (rows, columns)

For deeply nested structures or JSON where the records are inside a nested key:

pythondeep_nested.py
import pandas as pd
import json

# JSON like: {"status": "ok", "data": {"users": [{"id":1,"name":"Alice"}]}}
with open('api_response.json', 'r') as f:
    response = json.load(f)

# Extract the array from its nested path
users = response['data']['users']

# Or use json_normalize with record_path
# For JSON like: {"users": [{"name":"Alice","tags":["dev","python"]}]}
df = pd.json_normalize(
    response,
    record_path=['users'],           # Path to the array
    meta=['status'],                  # Top-level fields to include
    errors='ignore'                   # Skip missing fields
)

df.to_csv('users.csv', index=False)
4

Method 3: Manual Flattening for Custom Control

Sometimes you need full control over how nested data maps to columns — for example, handling arrays differently, or choosing a custom column naming scheme.

pythonmanual_flatten.py
import json
import csv

def flatten_dict(d: dict, parent_key: str = '', sep: str = '.') -> dict:
    """Recursively flatten a nested dictionary."""
    items = {}
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.update(flatten_dict(v, new_key, sep))
        elif isinstance(v, list):
            # Convert list to comma-separated string
            items[new_key] = ', '.join(str(i) for i in v)
        else:
            items[new_key] = v
    return items

def json_to_csv(input_file: str, output_file: str) -> None:
    with open(input_file, 'r') as f:
        data = json.load(f)

    # Flatten each record
    flat_records = [flatten_dict(record) for record in data]

    # Collect all unique keys (some records may have different keys)
    all_keys = []
    for record in flat_records:
        for key in record:
            if key not in all_keys:
                all_keys.append(key)

    with open(output_file, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=all_keys, extrasaction='ignore')
        writer.writeheader()
        for record in flat_records:
            # Fill missing keys with empty string
            writer.writerow({k: record.get(k, '') for k in all_keys})

    print(f"Converted {len(flat_records)} records to {output_file}")

# Usage
json_to_csv('data.json', 'output.csv')
5

Handling Large JSON Files

For JSON files too large to fit in memory, use streaming parsing with the ijson library:

pythonlarge_json_streaming.py
# pip install ijson
import ijson
import csv

def stream_json_to_csv(json_file: str, csv_file: str, first_n: int = None):
    """Stream large JSON files to CSV without loading into memory."""
    row_count = 0

    with open(json_file, 'rb') as jf, open(csv_file, 'w', newline='') as cf:
        writer = None

        # ijson.items streams each element of the top-level array
        for record in ijson.items(jf, 'item'):
            if writer is None:
                # Initialize writer with keys from first record
                fieldnames = list(record.keys())
                writer = csv.DictWriter(cf, fieldnames=fieldnames)
                writer.writeheader()

            writer.writerow(record)
            row_count += 1

            if first_n and row_count >= first_n:
                break

    print(f"Streamed {row_count} rows to {csv_file}")

# Process a 10GB JSON file without loading it all into memory
stream_json_to_csv('massive_dataset.json', 'output.csv')

pandas also supports chunking

For large files with pandas, use pd.read_json('file.json', lines=True, chunksize=10000) for JSON Lines format, or process in batches.
6

Common Issues and Fixes

Missing newline parameter

❌ Bad
# Missing newline='' parameter
with open('output.csv', 'w') as f:
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    # On Windows: produces extra blank lines between rows

Correct file opening

✅ Good
# Correct: always use newline='' with csv module
with open('output.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(data)

pd.DataFrame loses nested data

❌ Bad
# Passing raw object list (works but loses nested data silently)
data = json.load(f)
df = pd.DataFrame(data)  # Nested dicts become object columns

json_normalize handles nesting

✅ Good
# Use json_normalize for proper nested handling
import pandas as pd
data = json.load(f)
df = pd.json_normalize(data, sep='_')  # Flattens nested objects
7

Complete Production Example

pythonproduction_converter.py
"""
Production-ready JSON to CSV converter with error handling,
encoding support, and progress reporting.
"""
import json
import csv
import sys
import logging
from pathlib import Path

logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)

def flatten_record(record: dict, sep: str = '.') -> dict:
    def _flatten(obj, prefix=''):
        result = {}
        if isinstance(obj, dict):
            for k, v in obj.items():
                result.update(_flatten(v, f"{prefix}{k}{sep}" if prefix else f"{k}{sep}"))
        elif isinstance(obj, list):
            result[prefix.rstrip(sep)] = json.dumps(obj)
        else:
            result[prefix.rstrip(sep)] = obj
        return result
    return _flatten(record)

def convert_json_to_csv(
    input_path: str,
    output_path: str,
    flatten: bool = True,
    encoding: str = 'utf-8',
) -> int:
    input_file = Path(input_path)
    if not input_file.exists():
        raise FileNotFoundError(f"Input file not found: {input_path}")

    logger.info(f"Reading {input_file.stat().st_size / 1024:.1f} KB from {input_path}")

    with open(input_path, 'r', encoding=encoding) as f:
        data = json.load(f)

    if isinstance(data, dict):
        # Try to find the array inside common wrapper keys
        for key in ('data', 'results', 'items', 'records', 'rows'):
            if key in data and isinstance(data[key], list):
                logger.info(f"Found array at key '{key}'")
                data = data[key]
                break
        else:
            data = [data]

    if not data:
        logger.warning("No records found in JSON")
        return 0

    records = [flatten_record(r) if flatten else r for r in data]

    # Collect all unique fieldnames
    fieldnames = list(dict.fromkeys(k for r in records for k in r.keys()))

    with open(output_path, 'w', newline='', encoding=encoding) as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
        writer.writeheader()
        for record in records:
            writer.writerow({k: record.get(k, '') for k in fieldnames})

    logger.info(f"Wrote {len(records)} rows × {len(fieldnames)} columns to {output_path}")
    return len(records)

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: python converter.py input.json output.csv")
        sys.exit(1)

    count = convert_json_to_csv(sys.argv[1], sys.argv[2])
    print(f"Done: {count} rows converted")

Frequently Asked Questions

Related Python & Development Guides

Continue with closely related troubleshooting guides and developer workflows.