8 min read

How to Convert JSON to CSV (Including Nested JSON)

Convert JSON to CSV correctly: flatten nested objects, quote commas and line breaks per RFC 4180, handle arrays, and open the result cleanly in Excel.

By json2toon.co

To convert JSON to CSV, start with an array of objects. Flatten nested objects into dotted column names and join or explode arrays. Then build the header from every key that appears, and let a CSV library quote fields that contain commas, quotes or line breaks. For a quick conversion, paste your data into the JSON to CSV converter.

What is the fastest way to convert JSON to CSV?

Use the JSON to CSV tool. It runs in your browser, so the data never leaves your machine. It expects a top-level array of objects. A single object fails with "CSV output requires an array of objects", so wrap it in [ ] first. You can choose comma, semicolon, tab or pipe as the delimiter. Semicolon helps when Excel is set to a locale that uses commas as decimal separators. For the reverse direction, use CSV to JSON.

The converter does not flatten nested data for you. If your JSON has nested objects or arrays, read the next sections before converting.

What does RFC 4180 require?

RFC 4180 (October 2005) is the closest thing CSV has to a standard. It says: "Fields containing line breaks (CRLF), double quotes, and commas should be enclosed in double-quotes". A quote inside a field is escaped by doubling it ("b""bb"), records end with CRLF, and the MIME type is text/csv. On the JSON side, RFC 8259 requires JSON exchanged between systems to be UTF-8, so keep UTF-8 all the way through.

Don't build CSV with values.join(","). A name like Lin, Bo or a note with a line break will shift every column after it.

What happens if you skip flattening?

Take two orders with a nested customer, a tags array and a tricky note:

[
  {
    "id": 1001,
    "customer": { "name": "Ada Lovelace", "email": "ada@example.com" },
    "total": 49.99,
    "tags": ["new", "promo"],
    "note": "Leave at door"
  },
  {
    "id": 1002,
    "customer": { "name": "Lin, Bo", "email": "bo@example.com" },
    "total": 120,
    "tags": [],
    "note": "Says \"ring twice\"\nthen wait"
  }
]

Passing this straight to Papa Parse 5.7 (Papa.unparse(orders)) gives:

id,customer,total,tags,note
1001,[object Object],49.99,"new,promo",Leave at door
1002,[object Object],120,,"Says ""ring twice""
then wait"

The quoting is correct: the embedded quotes are doubled and the line break is inside quotes. But the customer data is lost as [object Object].

How do you flatten nested JSON for CSV?

Walk each object recursively and join keys with a dot. Arrays need a policy. Here we join them with a semicolon:

import Papa from "papaparse";

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

const csv = Papa.unparse(orders.map((o) => flatten(o)));

Output:

id,customer.name,customer.email,total,tags,note
1001,Ada Lovelace,ada@example.com,49.99,new;promo,Leave at door
1002,"Lin, Bo",bo@example.com,120,,"Says ""ring twice""
then wait"

There are three common ways to handle arrays, each with a trade-off:

  • Join into one cell (new;promo). Easy to read, but you need to split it again when parsing.
  • Explode into rows, one row per tag with the order fields repeated. Good for pivot tables, but it duplicates data.
  • Store as a JSON string (["new","promo"]). Lossless, but awkward in a spreadsheet.

Why are some columns missing from my CSV?

Papa Parse builds the header from the first object's keys. If a later object has extra keys, those values are silently dropped:

Papa.unparse([{ a: 1, b: 2 }, { a: 3, c: 4 }])
a,b
1,2
3,

Papa.unparse([{ a: 1, b: 2 }, { a: 3, c: 4 }], { columns: ["a", "b", "c"] })
a,b,c
1,2,
3,,4

Collect the union of keys across all rows and pass it as columns. Python's standard library does the same with DictWriter:

import csv, json

def flatten(obj, prefix=""):
    out = {}
    for key, value in obj.items():
        name = f"{prefix}.{key}" if prefix else key
        if isinstance(value, dict):
            out.update(flatten(value, name))
        elif isinstance(value, list):
            out[name] = ";".join(str(v) for v in value)
        else:
            out[name] = value
    return out

with open("orders.json", encoding="utf-8") as f:
    rows = [flatten(o) for o in json.load(f)]

columns = list(dict.fromkeys(k for row in rows for k in row))
with open("orders.csv", "w", newline="", encoding="utf-8-sig") as f:
    writer = csv.DictWriter(f, fieldnames=columns)
    writer.writeheader()
    writer.writerows(rows)

How do you open the CSV cleanly in Excel?

  • Encoding. Papa Parse does not add a byte-order mark. Excel may then misread accented characters in a UTF-8 file. Writing with utf-8-sig in Python, as above, or prepending  in JavaScript adds the BOM.
  • Formula injection. A cell starting with =, +, - or @ can run as a formula. Papa Parse's escapeFormulae: true turns =SUM(A1:A2) into '=SUM(A1:A2).
  • Types are lost. CSV has no types. Parsing our file back with Papa Parse returns "1001" as a string unless you set dynamicTyping: true, and with that option the empty tags cell comes back as null, not "". Leading zeros in IDs and zip codes are also at risk in Excel.

More on the format's quirks in understanding CSV.

When is CSV the wrong target?

If the data is going into an LLM prompt instead of a spreadsheet, you don't have to flatten it. TOON keeps the nesting. Here is the same order data, generated with @toon-format/toon 4.1.1:

orders[2]:
  - id: 1001
    customer:
      name: Ada Lovelace
      email: ada@example.com
    total: 49.99
    tags[2]: new,promo
    note: Leave at door
  - id: 1002
    customer:
      name: "Lin, Bo"
      email: bo@example.com
    total: 120
    tags: []
    note: "Says \"ring twice\"\nthen wait"

For purely flat tables, CSV wins slightly on size. In the official TOON benchmark (spec v4.1, 5,856 calls), CSV could only take part in the 109 flat-data questions out of 244, because it can't represent the rest. On those flat questions, CSV averaged 1,851 tokens at 62.2% accuracy, TOON 1,994 tokens at 63.1% and JSON 3,950 tokens at 60.3%. See CSV vs TOON for the full comparison, or try JSON to TOON.

Frequently Asked Questions

How do I convert nested JSON to CSV?

Flatten each object first. Turn nested objects into dotted column names such as customer.name, and join arrays into one cell or split them into extra rows. Then build the header from the union of all keys and write the rows with a CSV library that quotes fields correctly.

Why does my CSV show [object Object]?

The converter turned a nested object into a string without flattening it. Libraries such as Papa Parse do this when a cell value is an object. Flatten nested objects into separate columns before converting, or serialize them as JSON strings if you need to keep them in a single cell.

Which CSV fields need quotes?

RFC 4180 says fields containing line breaks, double quotes or commas should be enclosed in double quotes, and a double quote inside a field is escaped by doubling it. A good CSV library handles this for you, so avoid building CSV by joining strings with commas.

Should I send CSV or TOON to an LLM?

For purely flat tables, CSV is slightly smaller. For anything nested, TOON keeps the structure that CSV loses. In the official TOON benchmark on flat data, CSV used 1,851 tokens on average and TOON 1,994, with accuracy of 62.2% and 63.1% respectively.

Recommended Reading

JSONCSVConversionTutorialExcelData Format