DevKitHub

JSON & Data

JSON to CSV Converter — Flatten Nested JSON for Excel

Paste an array of objects to get a CSV with one row per object. Nested fields become dot-path columns like address.city, and any cell a spreadsheet would run as a formula is flagged.

download only
4 lines
3 lines
Table
2 rows × 5 columns
  • 1 of 2 rows leaves at least one cell empty because the field is missing. The header is every field seen in any row, so nothing is dropped.

The byte order mark goes into the downloaded file only, never into the text you copy. Excel in locales that write decimals with a comma expects a semicolon between fields, so choose Semicolon there. Going the other way? CSV to JSON reads CSV with the same quoting rules.

This tool runs entirely in your browser. Your input is never uploaded, stored or logged.

How it works

CSV is a flat table and JSON is not, so the work is flattening. Each nested object becomes columns named by path — address.city, address.geo.lat — and the header is the union of every path in every row, not just the first, so a field only some records carry still gets a column, next to its siblings. Arrays are the real decision. Kept as JSON in one cell, the default, a list of tags stays one value you can parse back; expanded by index, tags.0 and tags.1 become columns, which suits a short fixed list and not a long ragged one. An empty object or array is written as {} or [] so it is not mistaken for a missing field, while null and a missing field both become an empty cell, because CSV cannot tell them apart. A key that already contains a dot reads like a nested path, and if the two collide the later column gets a suffix and the result says so. JSON Lines, one object per line, works too.

A correct CSV can still be dangerous to open. Excel, Google Sheets and LibreOffice treat a cell beginning with =, +, -, @, a tab or a line break as a formula, so a customer name of =HYPERLINK(…) in an exported table becomes a live link, and OWASP describes payloads that exploit the spreadsheet or leak its contents. Every such cell is counted and reported whether or not you escape it. Escape formulas applies one of the two prefixes OWASP gives — an apostrophe, which spreadsheets display as text, or a tab inside quotes, which OWASP finds more reliable in Excel — and both change what a program reading the file will see, which is why it is off by default. Numbers are not formulas: a JSON -5, or the text "-5", is left alone, while "-5+3" and "+44 20 7946 0000" are flagged.

Two further problems sit either side of the conversion. After it, Excel opens a CSV that has no byte order mark in a legacy encoding, so café arrives as café; Microsoft’s guidance is that a UTF-8 CSV opens correctly when saved with a BOM, which Add BOM for Excel adds to the download and never to the text you copy. Before it, JSON.parse has already rounded any integer past 2^53: 12345678901234567890 becomes 12345678901234567000 before there is anything to convert. That cannot be repaired afterwards, so the source text is scanned for long integers, and any that were rounded are counted, with the first one shown. The fix is upstream — send ids as strings.

Common problems

Every example below is run against this tool in our test suite, so what it says here is what the tool actually does.

Row 1 is a number, not an object.

[1,2]
Why:
CSV needs named fields to build a header, and an array of plain values has none. An array of arrays fails the same way: it is shaped like a table but has no column names.
Fix:
Wrap each value in an object, as in [{"value": 1}, {"value": 2}], or paste the array of objects that holds your rows.

Expected property name or '}'

[{'id': 1, 'active': True}]
Why:
This is how Python prints a list of dicts, not JSON: single quotes, and True, False and None where JSON has true, false and null.
Fix:
Produce the text with json.dumps(rows) in Python rather than print(rows) or str(rows).

A long id ends in 000 in the CSV.

Why:
JSON.parse turns any integer past 2^53 into the nearest number JavaScript can hold, so 12345678901234567890 becomes 12345678901234567000 before conversion starts. Every tool built on JSON.parse does this; this one says so.
Fix:
Send the id as a string in the JSON, as in "id": "12345678901234567890". Strings are copied into the CSV exactly.

A spreadsheet ran a formula, or mangled a phone number.

Why:
A cell began with =, +, - or @, and the spreadsheet read it as a formula. That is formula injection when the value came from a user, and a phone number like +44 20 7946 0000 no longer appears as it was written.
Fix:
Turn on Escape formulas before handing the file to someone who will open it in a spreadsheet. The warning counts the affected cells even while it is off.

Accented letters show up as café in Excel.

Why:
Excel reads a CSV without a byte order mark in a legacy code page rather than UTF-8, so each non-ASCII character turns into two or three wrong ones.
Fix:
Turn on Add BOM for Excel and download again, or import the file through Data > From Text/CSV and choose UTF-8.

Frequently asked questions

How are nested objects turned into CSV columns?
Each nested field becomes a column named by its path, such as address.city, or items.0.sku when arrays are expanded. The header is the union of every path in every row, so nothing is dropped when rows have different fields.
How do I open the CSV in Excel without breaking accented characters?
Turn on Add BOM for Excel before downloading. Excel reads a CSV without a byte order mark in a legacy encoding, and with the BOM it reads UTF-8. If your Excel writes decimals with a comma, choose Semicolon as the delimiter as well.
What is CSV injection?
Spreadsheet software runs a cell that starts with =, +, -, @ or a tab as a formula, so data exported from a website can carry a formula that opens links or leaks the sheet’s contents. Escape formulas prefixes those cells as OWASP recommends, and the cells are counted even when it is off.
Why did my long numbers change?
JSON numbers are read as JavaScript numbers, which are exact only up to 9,007,199,254,740,991. Anything longer is rounded while the JSON is parsed, before any CSV is written. Put such ids in quotes in the JSON to keep every digit; the converter warns when it finds one.

Last updated