Convert Nested JSON to CSV: Flatten Objects and Arrays

You pull a response from Stripe, Shopify, HubSpot, or Salesforce, run it through a JSON-to-CSV converter, and open the result in Excel. Half your columns read [object Object] and your arrays collapsed into a single unreadable cell. The problem is not the converter; it is that CSV is a flat, two-dimensional grid and your JSON is a tree. There is no single correct way to project a tree onto a grid, so "flatten nested JSON to CSV" is really a decision, not a button. This guide covers the four flattening strategies, when each one applies, and the two traps that silently corrupt output.

Why nested JSON breaks naive CSV conversion

A CSV row is a flat list of values, one per column. JSON values can be objects (key/value maps) and arrays (ordered lists) nested to any depth. When a converter hits a nested object and does not know what to do, it falls back to the JavaScript default string coercion, which produces the literal text [object Object]. Arrays often get stringified too, or worse, dropped. According to the MDN documentation for Object.prototype.toString, that placeholder is the expected result of coercing a plain object to a string. The fix is to choose a deliberate mapping for each nested shape.

The four flattening strategies and when to use each

1. Dot notation for nested objects

When a field is a nested object with a small, predictable set of keys, expand each leaf into its own column and join the path with a dot. An address object becomes address.city, address.zip, and address.country. This is the right default for single nested objects such as a customer's address, a Stripe card object, or a Salesforce record's metadata. It is lossless for objects, keeps related fields adjacent, and round-trips cleanly. Use it whenever the nested object represents one thing, not a list of things.

2. Row expansion (explode) for arrays of records

When a field is an array of objects, such as an order's line_items or an invoice's charges, dot notation will not save you, because there can be any number of items. The correct move is to expand each array element into its own row and repeat the parent fields on every line. An order with two line items becomes two CSV rows that share the same order ID, customer, and date. This is the only strategy that preserves every value when the array length varies between records. It is how relational databases model one-to-many data, and it is what you want before loading into a pivot table or SQL.

3. Serialize to string for simple value arrays

When an array holds primitive values that you treat as a single attribute, such as a list of tags, labels, or product categories, exploding into rows is overkill and dot notation makes no sense. Join the values into one cell with a delimiter, for example "electronics; sale; featured". Pick a separator that does not collide with your CSV delimiter; semicolons are safer than commas here, and the whole cell must be quoted per the rules in the CSV quoting rules guide. This is lossy for analysis (you cannot easily filter on one tag) but perfect for human-readable export.

4. Indexed columns for fixed-size arrays

When an array is always the same length and the position carries meaning, such as RGB triplets, geographic coordinates, or a fixed set of quarterly figures, give each index its own column: color.0, color.1, color.2, or coords.lat and coords.lng if you rename them. This keeps every value sortable and filterable without exploding rows. Only use it when the length is genuinely fixed; applying it to a variable-length array produces ragged, mostly empty columns.

Worked example: an order with two line items

Here is a typical e-commerce order combining all four shapes: a nested object, an array of records, and a value array.

{
  "order_id": "1042",
  "date": "2026-06-15",
  "customer": { "name": "Dana Lee", "address": { "city": "Austin", "zip": "78701" } },
  "tags": ["priority", "gift"],
  "line_items": [
    { "sku": "TS-01", "qty": 2, "price": 19.99 },
    { "sku": "MUG-7", "qty": 1, "price": 9.50 }
  ]
}

Flattening this correctly means dot notation for customer, a serialized tags cell, and row expansion for line_items. The two line items produce two rows that repeat every parent field:

order_id,date,customer.name,customer.address.city,customer.address.zip,tags,line_items.sku,line_items.qty,line_items.price
1042,2026-06-15,Dana Lee,Austin,78701,"priority; gift",TS-01,2,19.99
1042,2026-06-15,Dana Lee,Austin,78701,"priority; gift",MUG-7,1,9.50

Now every line item is its own row, the address is split into queryable columns, and the tags live in one quoted cell. This loads cleanly into Excel, a pivot table, or a database table.

Two traps that silently destroy data

The inconsistent-keys trap

Many quick converters build the header row from the keys of the first object only. If record 1 has no discount field but record 50 does, the converter never creates a discount column and drops that data with no warning. Real API exports are full of optional fields, so this is common and costly. A correct tool walks every record in the dataset to build the union of all keys, then fills missing values with empty cells. Before trusting any conversion, confirm the column count matches your richest record, not your first.

The lossy-conversion trap

Flattening throws information away. Once you serialize tags into a string, explode arrays into repeated rows, or coerce a number like 9.50 (CSV may render it as 9.5), you generally cannot reconstruct the exact original tree from the CSV alone. Type information vanishes too, since everything in CSV is text. Always keep your source JSON. Run it through a JSON formatter first to validate structure and catch syntax errors before converting, and archive the original so the CSV stays a derived view, not your system of record.

Picking a strategy quickly

  • Single nested object (address, card, metadata): dot notation.
  • Array of records of varying length (line items, charges): row expansion.
  • Array of simple values you treat as one attribute (tags, labels): serialize to a delimited string.
  • Fixed-length positional array (RGB, coordinates): indexed columns.

Most real API payloads need a mix, exactly like the order above. For the foundations of delimiters, encoding, and spreadsheet quirks, the CSV data handling guide is worth a read before you ship exports to non-technical colleagues.

Convert it the right way, in your browser

Our CSV to JSON converter is bidirectional and built to avoid both traps: it walks every record to assemble the full set of column names so optional fields never disappear, handles nested objects and arrays, and runs 100% in your browser, so a Stripe or Salesforce payload with customer data never leaves your machine. Choose your flattening strategy first, keep the source JSON, then convert with confidence.

Frequently Asked Questions

That placeholder appears when a converter coerces a nested JSON object to a string without expanding it. Per MDN, it is the default Object.prototype.toString output. Fix it by flattening nested objects into dot-notation columns like address.city, or by expanding arrays of objects into separate rows so each leaf value lands in its own cell.

Use row expansion: emit one CSV row per array element and repeat every parent field on each line. An order with two line items produces two rows sharing the same order ID, date, and customer. This is the standard one-to-many representation and the only way to preserve all values when array length varies between records.

Many tools build the header row from only the first object's keys, so any field that appears only in later records is silently dropped. Use a converter that walks every record to build the union of all keys. Always verify the column count matches your richest record, not the first one, before trusting the output.

Not reliably. Flattening is lossy: serialized tag strings, exploded rows, and number formatting like 9.50 becoming 9.5 cannot always be reversed, and CSV stores everything as text without types. Keep your source JSON as the system of record and treat the CSV as a derived, read-only export.

If you treat tags as a single attribute, serialize them into one quoted cell with a delimiter that does not clash with your CSV separator, such as a semicolon: "priority; gift". If you need to filter or pivot on individual tags later, use row expansion instead so each tag occupies its own row.