← All Articles

How to Convert JSON to CSV in Python

July 20, 2026

Man typing on a laptop displaying lines of code at a wooden desk with a notebook, potted plant, and coffee mug in a bright office.

Working with data from APIs, databases, or web scraping often means dealing with JSON, a format that is great for machines but not always convenient for analysis or sharing. Converting JSON to CSV in Python is one of the most common data wrangling tasks, and the good news is that Python offers several straightforward ways to do it. Whether you are processing a handful of records or millions of rows from a production pipeline, the right approach depends on your data's complexity and size. This guide walks you through the standard library method, the Pandas shortcut, strategies for nested structures, and best practices for handling real-world edge cases.

Understanding JSON and CSV Data Structures

JSON (JavaScript Object Notation) stores data as key-value pairs organized into objects and arrays. A single JSON file can hold deeply nested hierarchies: objects inside arrays inside other objects, with no fixed schema. This flexibility is exactly why APIs and NoSQL databases favor it. But that same flexibility creates friction when you need tabular data.

CSV (Comma-Separated Values) is flat by design. Each row has the same number of columns, and every column has a header. Spreadsheet tools, SQL databases, and most data visualization platforms expect this kind of structure. The mismatch between JSON's tree-like shape and CSV's rigid rows is the core challenge you will solve during conversion.

Understanding this structural difference matters because it determines which conversion strategy to pick. If your JSON is already a flat list of uniform objects, a simple loop will do the job. If it contains nested arrays or inconsistent keys, you will need flattening logic or a library that handles normalization for you.

Converting JSON to CSV Using the Built-in csv Module

Python ships with both a json module and a csv module in the standard library, so you do not need to install anything extra for basic conversions. This approach gives you fine-grained control over how each field is written and works well for flat or lightly nested JSON.

Reading JSON Data from Files or APIs

Start by loading your JSON into a Python data structure. For a local file, json.load() reads the file object directly:

import json

with open("data.json", "r") as f:
 records = json.load(f)

If you are pulling data from a REST API, the requests library returns JSON with a single method call:

import requests

response = requests.get("https://api.example.com/users")
records = response.json()

At this point, records is typically a list of dictionaries. Confirm the structure by printing the first element. If the top level is a dictionary with a nested key that holds the actual list (common in paginated APIs), extract that key first: records = data["results"].

Writing Rows with DictWriter

The csv.DictWriter class maps dictionary keys to column headers automatically. Here is a complete example:

import csv

fieldnames = records[0].keys()

with open("output.csv", "w", newline="") as csvfile:
 writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
 writer.writeheader()
 writer.writerows(records)

A few things to watch for. If not every record contains the same keys, pass extrasaction="ignore" to skip unexpected fields, or build a union of all keys across records to avoid missing columns. Setting newline="" on Windows prevents blank rows from appearing between entries.

Leveraging Pandas for Fast Data Transformation

When your dataset is larger or you need to filter, rename, or transform columns before export, Pandas is the practical choice. A two-line script can replace dozens of lines of manual csv-writing code.

Loading JSON into a DataFrame

The pd.read_json() function accepts a file path, URL, or raw JSON string:

import pandas as pd

df = pd.read_json("data.json")

For API responses already loaded as a Python list of dicts, pass them to the DataFrame constructor instead:

df = pd.DataFrame(records)

Pandas infers data types on import, converting date strings to datetime objects and numeric strings to integers or floats. Check df.dtypes and df.head() to verify the result looks correct before exporting.

Exporting to CSV with to_csv()

Once the DataFrame is ready, a single call writes it out:

df.to_csv("output.csv", index=False)

Setting index=False prevents Pandas from adding an extra column of row numbers. You can also specify a different delimiter with sep="\t" for TSV files, select a subset of columns with the columns parameter, or compress the output directly with compression="gzip". These options make Pandas especially convenient when you need to transform JSON to CSV in Python as part of a larger data pipeline.

Handling Nested JSON Objects and Arrays

Real-world JSON rarely arrives as a clean, flat list. API responses from platforms like GitHub, Stripe, or Shopify routinely contain nested objects (an address inside a user record) and arrays (a list of tags or line items). You cannot write a nested dictionary directly into a CSV row without first flattening it.

Flattening Data with json_normalize

Pandas provides pd.json_normalize(), which recursively unpacks nested dictionaries into flat columns with dot-separated names:

from pandas import json_normalize

data = [
 {"name": "Alice", "address": {"city": "Denver", "state": "CO"}},
 {"name": "Bob", "address": {"city": "Austin", "state": "TX"}}
]

df = json_normalize(data)

The resulting DataFrame will have columns name, address.city, and address.state. You can rename these afterward with df.rename(columns={"address.city": "city"}) for cleaner headers.

For records that contain arrays, use the record_path parameter to specify which array to explode into rows, and meta to carry parent-level fields along:

df = json_normalize(data, record_path="orders", meta=["customer_id", "customer_name"])

This produces one row per order, with the customer information repeated on each row.

Custom Mapping for Complex Hierarchies

Sometimes json_normalize is not enough. If your JSON has irregular nesting, optional fields, or arrays of arrays, writing a custom flattening function gives you full control:

def flatten(record, parent_key="", sep="_"):
 items = {}
 for k, v in record.items():
 new_key = f"{parent_key}{sep}{k}" if parent_key else k
 if isinstance(v, dict):
 items.update(flatten(v, new_key, sep))
 elif isinstance(v, list):
 items[new_key] = json.dumps(v) # serialize arrays as strings
 else:
 items[new_key] = v
 return items

flat_records = [flatten(r) for r in records]

This recursive approach handles arbitrary depth. Arrays that cannot be meaningfully flattened get serialized as JSON strings so they still fit in a single CSV cell. You can then post-process those columns later if needed.

Best Practices for Large Datasets and Error Handling

Small files are forgiving. Large files and production workloads are not. A 2 GB JSON file will crash your script if you try to load it entirely into memory on a machine with 4 GB of RAM. Encoding mismatches and unexpected delimiters inside field values will silently corrupt your output.

Streaming Large JSON Files

The standard json.load() reads the entire file into memory at once. For large files, use the ijson library, which parses JSON incrementally:

import ijson

with open("large_data.json", "rb") as f:
 records = ijson.items(f, "item")
 for record in records:
 # process and write each record individually
 pass

Pair this with a csv writer that you open once and write to row by row inside the loop. This keeps memory usage constant regardless of file size. If you are working with Pandas, the chunksize parameter in pd.read_json() provides a similar streaming capability, returning an iterator of DataFrames instead of one massive frame.

Another option for very large datasets is to use line-delimited JSON (also called JSONL or NDJSON), where each line is a standalone JSON object. This format is trivially streamable: read one line, parse it, write it to CSV, repeat.

Managing Character Encodings and Delimiters

UTF-8 is the default for most modern APIs, but you will occasionally encounter Latin-1, Windows-1252, or Shift-JIS data. Specify the encoding explicitly when opening files:

with open("data.json", "r", encoding="utf-8-sig") as f:
 records = json.load(f)

The utf-8-sig variant strips the BOM (byte order mark) that some Windows tools prepend. On the CSV output side, if your data contains commas within field values, csv.DictWriter handles quoting automatically by default. But if you are generating files for a system that expects a different delimiter (semicolons are common in European locales), set delimiter=";" in the writer constructor.

Always validate your output. Open the resulting CSV in a text editor, not just a spreadsheet tool, to confirm that row counts match, special characters survived, and no fields shifted into the wrong column. A quick sanity check like comparing len(records) to the line count of your CSV (minus the header) catches most issues instantly.

Wrapping Up: Picking the Right Tool for the Job

Converting JSON to CSV with Python comes down to three paths. The built-in csv module works well for flat data and keeps your dependency list short. Pandas handles transformations, type inference, and export options with minimal code. For nested or irregular JSON, json_normalize and custom flattening functions bridge the gap between tree-shaped data and flat rows.

Whichever method you choose, keep a few principles in mind. Always inspect your JSON structure before writing conversion code. Handle encoding explicitly rather than hoping for the best. Stream large files instead of loading them whole. And validate your output with a quick row count and spot check.

These techniques will cover the vast majority of real-world scenarios you encounter, from one-off data exports to automated ETL jobs running on a schedule. Start with the simplest approach that fits your data, and reach for more complex tools only when the structure demands it.