data · 6 min read

How to turn nested JSON into a spreadsheet

An API gives you a tree. The person who asked for it wants a rectangle. Something has to give, and it is worth deciding what.

JSON holds objects inside objects inside arrays, to any depth. A spreadsheet holds rows and columns and nothing else. The conversion is not a format change, it is a restructuring, and every restructuring throws something away.

Converters that promise to do it automatically are choosing on your behalf. Knowing which choice they made is the difference between a useful file and thirty thousand columns.

First: what is a row?

Everything follows from this and it is the question people skip.

A response containing orders, each with several line items, can become one row per order or one row per line item. Both are correct. One is right for counting revenue by customer; the other is right for counting units by product.

Pick the level of the thing you are going to count. The rest of the document is then either columns on that row or repeated onto it.

Nested objects: dotted paths

An object inside an object flattens cleanly. A customer with an address containing a city becomes a column called customer.address.city.

This part is unambiguous and reversible. The only cost is column names that get long, and the mild nuisance that a key containing a dot becomes indistinguishable from a nesting level.

Arrays: the actual decision

Three strategies, and no converter can pick for you because the right answer depends on what you are going to do next.

Explode. One row per array item, with the parent's fields repeated on every row. An order with three line items becomes three rows carrying the same order number. This is what a database join produces and it is what pivot tables expect. The cost is that the file no longer has one row per order, so summing an order-level column triple-counts it.

Widen. One column per position: tags.0, tags.1, tags.2. Fine when every record has the same small number of items, and catastrophic otherwise — the column count is set by the single longest array in the document, and every other row leaves those columns empty.

Join. The whole array in one cell, separated by semicolons. Compact and readable by a person; no longer readable by a machine, and it breaks if any value contains the separator.

Explode when the items are the thing you care about. Join when the array is descriptive — tags, labels, categories. Widen almost never.

What a spreadsheet cannot represent

Types. CSV has none. A number, a string containing digits and a boolean all become text, and whatever opens the file guesses again from scratch — which is where leading zeros vanish and codes become dates.

Null against missing against empty. Three different states in JSON, one empty cell in a spreadsheet. If the difference matters, encode it deliberately.

Order and identity. Once an array is exploded, nothing in the file says those rows belonged to one record. Keeping the parent's identifier on every row is what lets anyone put it back.

Ragged objects

Real API responses rarely have identical keys on every record — optional fields are absent rather than null, and different record types carry different fields.

Flattening collects every distinct path across the whole document into the column set, so a handful of unusual records widens the file for everybody. A thousand records with eight fields each and one record with forty produces a forty-column file.

Worth checking the column count against the number of fields you expected before doing anything else with the result.

Doing it

A converter handles the mechanics once you have decided the row level and the array strategy. Formatting the JSON first is worth the extra step — you cannot choose a row level from a minified document, and reading the shape is most of the work.

Doing it in the browser matters more here than for most conversions, because the JSON that needs flattening is usually an API response containing customer records.

The short version

Decide what one row is before anything else. Nested objects become dotted columns and that part is safe. Arrays are the real decision: explode when the items matter, join when they are labels, and treat the CSV as an export rather than as the source.

Questions

How do I convert nested JSON to CSV?

Decide what one row represents, then flatten everything else into columns. Nested objects become dotted column names — address.city — and arrays either become one row each, or one column each, or a single joined cell. Which of those you pick is the whole decision, and no converter can make it for you.

What happens to arrays when flattening JSON?

One of three things. Exploded: one row per item, with the parent’s fields repeated on each. Widened: one column per position, tags.0, tags.1, which breaks the moment a record has more items than the widest one you planned for. Joined: the whole array in a single cell, separated by semicolons, which is readable and no longer machine-readable.

Why does my flattened CSV have thousands of columns?

The widening strategy met a record with a long array, or the objects have inconsistent keys. Every distinct path in the whole document becomes a column, so one unusual record adds columns to every row. Exploding into more rows usually gives a more useful file than widening into more columns.

Is flattening reversible?

Dotted paths from nested objects reverse cleanly. Exploded arrays do not — once the parent fields are repeated across rows, nothing in the file records that those rows were one record. Keep the JSON as the source and treat the CSV as an export.

How do I handle null and missing keys?

They are different and a spreadsheet cannot tell them apart. An explicit null and an absent key both become an empty cell, and so does an empty string. If that distinction matters in your data, decide on a marker before exporting rather than discovering the ambiguity afterwards.

Tools from this guide