How to Convert Nested JSON to CSV (With Examples)
Most JSON-to-CSV tools break on nested objects. Learn how dot notation flattening works and convert nested JSON to CSV instantly.
- json
- csv
- data
- conversion
- api
title: "How to Convert Nested JSON to CSV (With Examples)" description: "Most JSON-to-CSV tools break on nested objects. Learn how dot notation flattening works and convert nested JSON to CSV instantly." date: "2026-05-31" tags: ["json", "csv", "data", "conversion", "api"] relatedTool: "json-csv-converter" published: true
You hit an API, get back a JSON array of objects, and need it in a spreadsheet for someone downstream. Simple — until the objects are nested. Most online converters silently output [object Object] in your CSV cells or quietly drop the nested fields entirely. Here's why nesting breaks CSV and how dot notation flattening solves it cleanly, with runnable code for when you need to automate the conversion.
The Problem: Why Nested JSON Breaks CSV Converters
Consider this API response:
[
{
"id": 1,
"name": "Ada Lovelace",
"address": {
"city": "London",
"country": "UK"
}
}
]
A naive converter produces this:
id,name,address
1,Ada Lovelace,[object Object]
The address object was stringified with JavaScript's default toString, which returns the useless [object Object]. What you actually want is:
id,name,address.city,address.country
1,Ada Lovelace,London,UK
CSV is a flat format — every cell is a single value, every column is a single field. Nested objects have to be flattened into dot-notation column names so each nested property lands in its own column. Once the schema is flat, everything downstream — Excel, Google Sheets, pandas, SQL COPY — handles it correctly.
How Dot Notation Flattening Works
The rule is simple: join the path from the root to the value with dots, and use that as the column name.
Level 1 — one nested object:
{ "user": { "name": "Ada" } }
→ Column: user.name
→ Value : Ada
Level 2 — multiple levels deep:
{ "user": { "address": { "city": "London" } } }
→ Column: user.address.city
→ Value : London
Level 3 — arrays of primitives get flattened by index:
{ "tags": ["admin", "dev"] }
→ Columns: tags.0, tags.1
→ Values : admin, dev
Level 4 — arrays of objects are the hardest case, and you have two reasonable choices:
{ "orders": [{ "id": 1 }, { "id": 2 }] }
- Option A — index columns:
orders.0.id,orders.1.id. Keeps the row count constant, but the column count grows with the array length and every row has a lot of empty cells. Rarely what you want. - Option B — row expansion: each array element becomes its own row, with the parent object's scalar fields repeated on each row. Downstream tools can group and aggregate as needed. This is almost always what people actually want when the goal is spreadsheet analysis.
If the array holds objects, prefer Option B. If it holds primitives (like tags), Option A is fine because the length is usually small and known.
Step-by-Step: Convert Nested JSON to CSV
The manual flow using CodeScrub:
- Paste your nested JSON into the CodeScrub JSON ↔ CSV Converter.
- Confirm Flatten nested is on (it's the default).
- Copy the CSV output, or hit Download to grab a
.csvfile.
The nested fields become dot-notation columns automatically, mixed-shape rows get aligned, and everything runs in your browser — no upload, no size limit beyond what your browser can hold in memory.
Handling Mixed Schemas
Real-world JSON arrays rarely have perfectly uniform objects. Some records have fields others don't; some fields only appear conditionally. When you flatten, the union of all keys across all objects becomes the column set, and objects missing a given field get an empty cell. No data is lost, and the resulting table is still perfectly rectangular — which is what spreadsheet tools require.
[
{ "id": 1, "name": "Ada", "phone": "555-1234" },
{ "id": 2, "name": "Bob" }
]
Flattens to:
id,name,phone
1,Ada,555-1234
2,Bob,
Bob's phone cell is empty — not null, not the string "null", just empty. Excel and Google Sheets both read that as blank, which is what the spec says the value should be.
Doing It in Code (Python & JavaScript)
For automating the conversion in a pipeline, both ecosystems have solid options.
Python with pandas — the most common approach:
import pandas as pd
import json
with open("data.json") as f:
data = json.load(f)
df = pd.json_normalize(data, sep=".")
df.to_csv("output.csv", index=False)
json_normalize handles arbitrary nesting depth with the sep argument controlling the separator. Pass record_path=["orders"] if you also want Option B row expansion for a specific array field, and pair it with meta=["id", "name"] to carry parent-object scalar fields onto every expanded child row — the combination is exactly what you want for turning an API response of "customers with orders" into a flat "one row per order" report.
JavaScript with a manual flattener — for when you don't want a runtime dependency:
function flatten(obj, prefix = "") {
return Object.keys(obj).reduce((acc, key) => {
const path = prefix ? `${prefix}.${key}` : key;
const value = obj[key];
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
Object.assign(acc, flatten(value, path));
} else {
acc[path] = value;
}
return acc;
}, {});
}
This version handles nested objects but leaves arrays intact — extend the Array.isArray branch if you want index-based flattening or row expansion. For anything more than a one-off script, reach for a well-tested library (flat, json2csv, papaparse with a pre-flattening step) rather than rolling your own — the edge cases around nulls, dates, and circular references add up.
Common Pitfalls
Flattening looks straightforward until you meet real production data. A handful of things go wrong more often than they should — worth scanning this list once before you point a converter at anything large:
- Circular references. API responses won't have them, but objects assembled in code sometimes do — a user with a
bestFriendwho points back at the user. Naive recursive flatteners blow the stack. If your data comes from an object graph rather than JSON parsing, check for cycles first. - Very deep nesting produces unreadable column names. A column called
data.result.items.0.metadata.provenance.source.nameisn't useful to anyone. Cap flattening at 2–3 levels and leave deeper structures as JSON-encoded strings inside a single cell if the downstream consumer can parse them. - Array expansion blows up row counts. Ten parent objects, each with an array of 100 items, becomes 1,000 CSV rows the moment you expand. That's often fine — sometimes it's a memory problem. Estimate before you convert.
- Null, missing, and empty string all render as an empty cell. They mean different things in JSON but look identical in CSV, and coercing them back into distinct values on the reading side requires downstream logic you have to write. If preserving the distinction matters, consider a different format (Parquet, Avro, JSON Lines) instead of CSV.
- Delimiter collisions. If any of your string values contain commas, tabs, or whatever delimiter you're using, they must be quoted. Every real CSV library does this; your one-off
map(row => row.join(","))script does not. Use a library.
Bottom line
Nested JSON to CSV is a solved problem once you understand dot notation flattening: join the path with dots for the column name, expand arrays of objects into rows when you want them analyzable, and use empty cells for missing values. Paste your data into the CodeScrub JSON ↔ CSV Converter to see it work end-to-end — it handles every edge case discussed above, including the array-of-objects row expansion, and does it entirely in the browser.