Skip to content
Tools

JSON to CSV: How to Handle Nested Objects and Arrays

· 4 min read

To convert nested JSON to CSV, flatten each object so nested keys become dot-notation columns (address.city), then decide how arrays should be represented: joined into one cell, spread into numbered columns, or exploded into one row per item. The JSON to CSV converter flattens objects automatically and runs in your browser.

Why nested JSON does not fit CSV

CSV is a flat grid: one row per record, one value per cell. JSON is a tree. A record like this has an object inside it and a list inside that:

[
  {
    "id": 1,
    "name": "Ada",
    "address": { "city": "London", "zip": "N1" },
    "tags": ["admin", "dev"]
  },
  {
    "id": 2,
    "name": "Linus",
    "address": { "city": "Portland" },
    "tags": []
  }
]

Every conversion has to answer two questions: what are the columns, and what goes in a cell when the value is not a scalar.

Step 1: flatten objects into dot-path columns

Nested objects are the easy part. Walk the tree and join keys with a dot. Collect the union of keys across all records, because the second record has no address.zip:

id,name,address.city,address.zip,tags
1,Ada,London,N1,"[""admin"",""dev""]"
2,Linus,Portland,,[]

Missing values become empty cells. Note that an empty cell cannot distinguish a missing key, an empty string and null; if that difference matters downstream, keep JSON or use an explicit marker.

Step 2: choose an array strategy

Strategytags outputGood forDownside
JSON in cell["admin","dev"]Lossless round tripsHard to read or filter in a spreadsheet
Joinadmin; devHuman-readable reportsAmbiguous if items contain the separator
Index columnstags.0, tags.1Short arrays of known lengthColumn count grows with the longest array
Explode rowsOne row per tagPivot tables, SQL importRepeats the parent fields on each row

Arrays of objects, such as order line items, are usually best exploded: one row per item, with the parent ID repeated so you can join back. If you only need part of the document, extract it first with the JSONPath tester, for example $.orders[*].items[*].

Doing it in code

Python with pandas

import json
import pandas as pd

with open("users.json") as f:
    data = json.load(f)

df = pd.json_normalize(data)          # address.city, address.zip, tags
df.to_csv("users.csv", index=False)

# One row per tag, keeping id and name
tags = pd.json_normalize(data, record_path="tags", meta=["id", "name"])

json_normalize flattens nested objects with a . separator by default (sep changes it) and leaves lists as Python lists in the cell. record_path explodes a list into rows; note that records with an empty list produce no rows.

jq on the command line

jq -r '
  ["id","name","city","tags"],
  (.[] | [.id, .name, .address.city, (.tags | join("; "))])
  | @csv' users.json

@csv quotes strings and escapes embedded quotes for you; -r prints raw text instead of JSON strings. You pick the columns explicitly, which is verbose but predictable.

JavaScript

function flatten(obj, prefix = "", out = {}) {
  for (const [k, v] of Object.entries(obj)) {
    const key = prefix ? `${prefix}.${k}` : k;
    if (v && typeof v === "object" && !Array.isArray(v)) flatten(v, key, out);
    else out[key] = Array.isArray(v) ? JSON.stringify(v) : v;
  }
  return out;
}

const rows = data.map((r) => flatten(r));
const cols = [...new Set(rows.flatMap(Object.keys))];
const esc = (v) => (v == null ? "" : /[",\n\r]/.test(String(v))
  ? `"${String(v).replace(/"/g, '""')}"` : String(v));
const csv = [cols.join(","), ...rows.map((r) => cols.map((c) => esc(r[c])).join(","))].join("\r\n");

CSV pitfalls that cause real bugs

  • Quoting. Per RFC 4180, a field containing a comma, quote or line break must be wrapped in double quotes, and quotes inside are doubled (""). Hand-written join(",") breaks on the first address with a comma.
  • Formula injection. Spreadsheet apps may treat a cell beginning with =, +, - or @ as a formula. If the CSV contains user-supplied text and will be opened in Excel or Google Sheets, prefix such cells with a single quote. The JSON to CSV converter does this by default and leaves plain numbers like -12.5 alone.
  • Encoding. Excel on Windows may misread UTF-8 without a byte order mark, turning accented characters into mojibake. Add a BOM or import via Data > From Text/CSV.
  • Leading zeros and long numbers. Spreadsheets turn 00123 into 123 and long IDs into scientific notation. The CSV is fine; the app's import is not. Import those columns as text.
  • Delimiter. Some locales expect ; because , is the decimal separator.

Going back: CSV to JSON

Converting back with the CSV to JSON tool gives you strings unless types are inferred, and dot-path headers are not automatically re-nested by every converter. If you need a faithful round trip, keep nested values as JSON text in the cell and parse them on the way back.

FAQ

How do I convert nested JSON to CSV?

Flatten nested objects into dot-notation columns such as address.city, then join, index or explode arrays. A converter like JSON to CSV does the flattening for you.

How do I convert JSON to CSV in Python?

Load it with json.load, flatten it with pandas.json_normalize, then call to_csv("out.csv", index=False).

How are arrays handled when converting JSON to CSV?

Common choices are keeping the array as JSON text in one cell, joining the items with a separator, numbered columns (tags.0, tags.1) or one row per item.

Can I open the CSV in Excel safely?

Yes, if cells that start with =, +, - or @ are neutralized and the file is UTF-8 with a BOM or imported through Excel's text import.

Why does my converted CSV have empty cells?

Not every record has every key. Columns are the union of all keys, so records without a key get an empty cell.

More JSON guides