# JSON to CSV: Flatten Records into Rows

- Author: FastBankConvert (https://fastbankconvert.com)
- Published: 2026-10-17
- Category: CSV, JSON and text output
- Canonical: https://fastbankconvert.com/blog/json-to-csv

> Convert JSON to CSV by flattening records into rows: nested fields into columns, arrays into extra rows, and a header row from the keys.
Going from JSON to CSV means flattening: each record in the JSON array becomes one row, each key becomes a column, nested objects turn into columns with joined names such as account.number, and arrays inside a record either spread into extra rows or get joined into one cell. The header row is the full set of keys.

## Building the CSV header row from JSON keys

The CSV header row comes from the keys of the JSON records. Collect every key that appears in any record, not only the first one, and fix an order for them. Each key becomes one column name. A record missing a key gets an empty cell in that column, so every row still has the same number of fields.

Collecting keys from every record matters because JSON records are not required to match. One transaction may carry a `memo` key and the next may not. A converter that reads only the first record builds a header without `memo`, and every memo in the file is silently dropped.

Order needs a decision too. [RFC 8259, the JSON standard](https://www.rfc-editor.org/rfc/rfc8259), defines an object as an unordered collection of name and value pairs, so the order keys appear in one record is not a promise about the next. Pick a column order yourself, usually the order a person would read: date first, then description, then amounts. Then write every row in that order.

Duplicate keys inside one object are a trap to know about. The same RFC says names within an object should be unique, and software varies in what it does when they are not. If a record has two `amount` keys, decide which one wins before converting, or report the record as bad.

## How do nested JSON objects become CSV columns?

Nested objects become columns by joining the path of keys with a separator, usually a dot. A record with `"account": {"number": "4417", "type": "checking"}` produces two columns, `account.number` and `account.type`. Deeper nesting just adds more parts to the name, such as `merchant.address.city`.

The dotted names keep the meaning of each value visible in the header. They also make the trip reversible: a script can split `account.number` on the dot and rebuild the nested object later. Underscores work as a separator too, but pick one and use it everywhere, because a key that already contains an underscore becomes ambiguous if underscores also mean nesting.

Here is a worked example. The values are made up for illustration. Two JSON transaction records:

```json
[
  {"date": "2026-04-02", "amount": "-18.40",
   "account": {"number": "4417", "type": "checking"},
   "tags": ["food", "work"]},
  {"date": "2026-04-03", "amount": "1200.00",
   "account": {"number": "4417", "type": "checking"},
   "tags": []}
]
```

Flattened with nested fields as dotted columns and the array spread into rows:

| date | amount | account.number | account.type | tags |
| --- | --- | --- | --- | --- |
| 2026-04-02 | -18.40 | 4417 | checking | food |
| 2026-04-02 | -18.40 | 4417 | checking | work |
| 2026-04-03 | 1200.00 | 4417 | checking | |

The first record produced two rows because it had two tags. The second produced one row with an empty tag cell, so it was not lost. That choice about arrays is the subject of the next section.

## How do JSON arrays turn into extra CSV rows?

An array inside a record has two common treatments. Spread it into extra rows, repeating the record's other fields on each row, or join its items into one cell with a separator such as a semicolon. Extra rows suit arrays of things you will count or filter. A joined cell suits short labels you only need to read.

Spreading into rows has a cost that catches people out: the parent's numbers repeat. In the example above, the -18.40 purchase appears twice, once per tag. Sum the amount column and the total is wrong by 18.40. If you spread arrays, add a column that identifies the original record, such as a transaction ID, and total by distinct ID. Or keep the amounts in one file and the tags in a second file that links to it.

Joining into one cell keeps one row per record and the totals honest. The tags column would read `food;work` for the first record and be empty for the second. The cost is that a spreadsheet filter cannot easily pick out every row tagged `work`.

A record with two arrays makes spreading worse. Spreading both multiplies the rows: three tags and two attachments give six rows. In that case, spread at most one array and join the rest, or split them into separate files.

The flattened result is not only for spreadsheets. A small table can also be written out for documentation or a chat message, and [turning a PDF table into Markdown](/blog/pdf-table-to-markdown) covers that plain-text table format.

## JSON nulls, commas and line breaks that break a CSV

Three kinds of JSON value need care on the way to CSV. A `null` usually becomes an empty cell. A string containing a comma or a double quote has to be wrapped in double quotes, with inner quotes doubled. A string containing a line break must be quoted too, or it splits one row into two.

Null and empty are different in JSON and the same in a plain CSV. A memo of `null` means "no memo"; a memo of `""` means "a memo that is blank". Most of the time nobody cares. If a later step does, write a fixed marker such as `NULL` for nulls and say so in a note with the file.

Line breaks are the value most likely to wreck a file. JSON stores a newline in a string as `\n`, and a converter writes it out as a real line break inside the CSV cell. A careful reader handles that because the cell is quoted. A simple reader, or a person splitting on lines, sees a new row that starts halfway through a description. [Line breaks inside CSV cells](/blog/csv-line-breaks-in-cells) explains how to keep them or strip them safely.

Numbers deserve a check as well. JSON numbers can carry more precision than a spreadsheet keeps, and money stored as a JSON number can pick up rounding. Keeping amounts as strings in the JSON, as in the example above, passes them through to the CSV exactly as written.

Once the CSV exists, checking it next to its source is easier with both visible at once. On a tablet, [viewing a PDF and Excel side by side on an iPad](/blog/ipad-pdf-and-excel-side-by-side) covers the split-screen setup. FastBankConvert, which is getting ready to open, plans JSON and CSV output from bank statements, so neither file has to be produced by hand; [Get started](/get-started) puts you on the list.
