Skip to content
jsoncsvonline

Cómo el JSON anidado se convierte en columnas CSV

Publicado

Cómo los objetos y arrays anidados se convierten en columnas CSV: rutas con puntos, límites de profundidad y cuatro estrategias de matrices.

Quick answer

To flatten nested JSON into CSV, traverse the object hierarchy recursively and assign each leaf value to a compound column named by its full object path (for example, customer.name). Arrays inside records can be expanded into indexed columns (tags.0, tags.1), unwound into duplicate parent rows, joined into a delimited cell, or retained as raw JSON text. You can customize the depth limit, delimiter, and array mode locally using our free JSON to CSV converter.


CSV is a rectangle. JSON is a tree. Every JSON-to-CSV converter is therefore making decisions on your behalf about how to flatten one into the other, and the frustrating ones are the tools that make those decisions silently.

Here is the record used throughout this guide:

{
  "id": 1,
  "customer": { "name": "Ada", "city": "London" },
  "tags": ["new", "vip"]
}

Objects become dotted column names

By default, every leaf value gets a column named by the path that reaches it. Nothing is dropped and nothing is guessed:

id customer.name customer.city tags.0 tags.1
1 Ada London new vip

Two things about that naming are adjustable. The separator is a dot by default; if your keys already contain dots, switch it to _ or / so the column names stay unambiguous. And array indices can be written tags.0 or tags[0] — the bracket style is what most SQL and BI tools expect if you plan to parse the header row later.

Depth limits

maxDepth caps how far the flattening walks. Levels below the cap are written as compact JSON text in a single cell instead of being expanded.

This matters when a record has one deeply nested field among otherwise flat data — a metadata blob, say, whose shape differs per record. Expanded, it produces hundreds of mostly empty columns. Capped at the level above it, it stays one column you can deal with separately.

Zero means no limit, which is the default.

Arrays are the real decision

There is no correct way to put a list into a cell, so this is a choice rather than a default worth defending. Four modes, all producing valid output from the same record:

One column per index. tags.0, tags.1, and so on, as in the table above. Predictable, and fine when arrays are short and fixed-length. Bad when one record has 200 tags, because the header is then 200 columns wide for everyone.

One row per item. The parent’s scalar fields repeat down the rows:

id customer.name customer.city tags
1 Ada London new
1 Ada London vip

This is the shape you want before a database import or a pivot table. Note that it multiplies your row count: a record with a 50-item array becomes 50 rows, and two independent arrays in one record multiply against each other.

Joined into one cell. tags becomes new, vip with a separator you choose. One row per record, human-readable, and lossy if a value contains the separator.

Kept as JSON text. tags becomes ["new","vip"]. Ugly to read, but the only mode that survives a round trip — use it when something downstream is going to parse the cell.

There is also a header/detail mode, which repeats the parent’s scalars as a header block above each nested array’s rows. It exists because some reporting tools produce that shape and people need to match it; it is not a good general choice, because the result is not a rectangle.

When records do not all have the same keys

The header is the union of every key found. A record missing one gets an empty cell — no error, no dropped record.

The subtlety is how much of the file gets inspected before the column set is fixed. Scanning a sample is much faster, and it is what most converters do without telling you. If a key appears for the first time in record 40,000, a sampled scan will not have it, and the column will be missing from the output entirely.

That is why full discovery is a switch here, and why it is worth turning on for irregular data. A slower conversion is a far smaller problem than a silently absent column.

Empty objects and nulls

An empty object or array can be written as a blank cell or as its literal {} / []. Blank is right when the CSV is going to a person or a spreadsheet; the literal is right when a downstream parser needs to distinguish “absent” from “present but empty”.

null is separate again, and separately configurable: you can write it as an empty cell, as the text null, or as whatever your database import expects — \N for a PostgreSQL COPY, for instance.

Finding the array in the first place

Real JSON is rarely a bare top-level array. It is usually an object with metadata, a status field, and the records you actually want somewhere inside:

{
  "ok": true,
  "meta": { "page": 1, "total": 4183 },
  "data": { "orders": [{ "id": 1 }, { "id": 2 }] }
}

The array worth converting here is data.orders. This page finds it and tells you which path it picked, so you can correct it if the guess is wrong — which happens when a document contains two plausible arrays. Converters that only accept a top-level array make this your problem, and the usual workaround is hand-editing the file to hoist the array out.

An object used as a lookup table — keys as ids, values as records — can also be converted directly, with the keys becoming a column.