Menu

Search toolsChangelog

to move to openDescribe the problem, not the tool

Turn JSON records into spreadsheet rows

Turns a JSON object, an array of objects or an array of arrays into a UTF-8 CSV or tab-separated file. It can flatten nested object paths, protects formula-like spreadsheet cells by default, and never uploads the source.

A JSON object, an array of objects, or an array of arrays up to 20 MB. It is parsed only in this tab.

Flatten turns profile.city into its own column. Arrays stay as JSON so one long list cannot create thousands of columns.

Comma is standard CSV. Semicolon works better where commas are decimal marks; tab makes a TSV-style file.

Prefix text beginning with =, +, - or @ so opening untrusted data in a spreadsheet cannot run it as a formula.

Rows written
One row for each object or inner array; a single top-level object becomes one row.
Columns written
Input shape
Output size (bytes)

How it works

What this works out

JSON and CSV do not disagree about punctuation; they disagree about shape. JSON can nest objects and arrays at any depth, omit a field from one record, and keep numbers, booleans and null as separate types. CSV is a rectangle of text cells. Converting between them therefore requires a declared answer to three questions: what counts as a row, which headings exist, and what happens to a value that is not already flat text.

This tool makes those decisions visible. It accepts only top-level structures that have an honest row model, discovers a stable header across the complete set, and either keeps nested values intact as JSON or flattens object paths. The downloaded bytes are then ordinary UTF-8 CSV or TSV, not a private format that needs this site to read it back.

The method

The row model comes entirely from the top level:

JSON shapeCSV interpretation
{ "name": "Ada" }One object, one row
[{ "name": "Ada" }, { "name": "Grace" }]Each object is a row; keys are headings
[["Ada", 36], ["Grace", 37]]Each inner array is a row; headings are column_1, column_2, and so on
[{}, []]Refused: the rows do not share one model
42, null or []Refused: there is no usable table

For object rows, the header is not taken from the first record alone. Real API data is sparse: the tenth record may be the first one with a middleName or invoice.vatNumber. Every object is walked, and the first appearance of a key adds it to the end of the header. That keeps the order predictable without silently losing later fields.

The two nested modes answer a different question:

Keep as JSON in one cell preserves a nested value as compact JSON. A value such as { "city": "Berlin", "postcode": "10115" } occupies one quoted cell and can be parsed back without guessing which headings belonged to it.

Flatten objects to dot paths makes address.city and address.postcode separate columns. Dots and backslashes already present in a key are escaped, so the literal key address.city becomes address\.city and cannot collide with an object called address containing a key called city. Arrays still remain JSON because position zero is not a field name.

Once each row has values under stable headings, the CSV rule is small but strict:

plain                         → plain
contains,comma                → "contains,comma"
say "hello"                   → "say ""hello"""
two lines                     → "two
                                 lines"

A quote inside a field doubles. A field containing the separator, a quote or a line break is surrounded by quotes. Rows end with carriage-return plus newline, which is the convention described for CSV and the one spreadsheet software handles consistently.

Spreadsheet formula protection

CSV has no type information. When spreadsheet software sees a cell beginning with =, +, - or @, it may interpret the text as a formula rather than as data. That matters when a name, note or imported API field came from somebody else: exporting untrusted text can turn opening the resulting spreadsheet into an action.

Protection is therefore on by default. A JSON string such as "=2+2" becomes '=2+2, which a spreadsheet treats as text. A JSON number such as -2 remains -2, because it is genuinely numeric rather than a string whose meaning is being inferred. Headings receive the same check; field names can be untrusted data too.

Before you read on

A later JSON record introduces a field called email that the first record did not have. What should happen to the CSV?

  • That silently loses real data, and sparse API records make it common.

  • Yes. The header is the union of keys in first-seen order.

  • That shifts a value under the wrong heading and makes the table look valid while being false.

Add one email column at the point that key is first discovered, and write an empty email cell for records that do not carry it. A CSV header must describe the complete table, not merely its first row. Taking only the first object's keys is a tempting shortcut because it is fast and produces valid-looking output, but sparse JSON makes it a data-loss bug: a field that appears later vanishes with no error. The converter therefore scans every row for the union of keys before it writes the header.

A worked example

The unit test converts these two records with flattening, comma separation, the UTF-8 marker and formula protection enabled:

[
  {
    "name": "Ada, Ltd.",
    "active": true,
    "note": "Line 1\nLine 2",
    "profile": { "city": "London" }
  },
  {
    "name": "=2+2",
    "active": false,
    "profile": { "city": "Paris" }
  }
]

The result is:

name,active,note,profile.city
"Ada, Ltd.",true,"Line 1
Line 2",London
'=2+2,false,,Paris

Four details are doing real work:

  1. profile.city is a flattened path, not an invented label.
  2. The comma and the line break force their fields to be quoted.
  3. The second record has no note, so its note cell is empty rather than allowing Paris to slide one column left.
  4. =2+2 receives an apostrophe because it came from a JSON string; the boolean values remain the typed text true and false.

The physical file is 95 bytes: 92 bytes of visible CSV and line endings plus the three-byte UTF-8 marker. The test asserts the entire string byte for byte, all four output figures, the filename, and the MIME type. Separate tests cover changing delimiters, TSV naming, union columns, nested JSON cells, escaped path segments, missing values, adversarial width, progress and cancellation.

What it does not do

It does not infer a relational database from arbitrary JSON. Nested arrays are not exploded into child tables, objects are not joined by guessed identifiers, and a primitive top level is not wrapped in an invented value column. Those can all be useful operations, but each needs a schema decision the file itself does not supply.

It also cannot preserve the distinction between null and missing in CSV. Both become an empty cell because an empty cell carries no type. Writing the word null would preserve one value at the cost of making a real string "null" indistinguishable, so that convention would need to be chosen by the importer, not silently imposed here.

Finally, it does not evaluate formulas, dates or locale-formatted numbers. A JSON number is written with JSON’s decimal syntax, a string stays a string, and the spreadsheet decides how to display it after import. The converter’s job is to move data into stable rows without changing what the source said.

How it is done

  1. Decode the file as UTF-8, discard a leading UTF-8 marker when present, and parse it with the browser's built-in JSON parser. Empty or syntactically invalid input is refused with the parser's reason.
  2. Decide the row model from the top level. One object is one row; an array of objects is many named rows; an array of arrays is many positional rows. Mixed row shapes are refused rather than guessed.
  3. For object rows, collect the union of keys in first-seen order so a field that appears only in a later record still gets one stable column and earlier rows get an empty cell there.
  4. When flattening is selected, walk nested objects into escaped dot paths such as profile.city. Keep arrays as JSON in one cell because their length is data, not a dependable set of columns.
  5. Render null or a missing field as an empty cell, booleans and numbers as JSON values, strings as text, and any remaining object or array as compact JSON.
  6. When formula protection is on, prefix string cells and headings whose first meaningful character is =, +, - or @ with an apostrophe. Real JSON numbers, including negative numbers, remain numeric.
  7. Double every quote inside a field and wrap a field in quotes when it contains the chosen separator, a quote or a line break. End every row with CRLF as the CSV convention specifies.
  8. Add the optional three-byte UTF-8 marker, encode the complete text, and offer .csv for comma or semicolon output or .tsv for tabs. Nothing is sent away at any point.

What it assumes

  • Supported top-level shapes are one object, an array containing only objects, or an array containing only arrays. A primitive, an empty array or a mixture of row shapes has no unambiguous table and is refused.
  • Object columns are the union of keys across every row, ordered by the first appearance of each key. Missing and null are both empty CSV cells because CSV has no distinct null value.
  • Flattening descends through objects only. Arrays remain compact JSON in one cell; expanding list positions would let one unusually long row create thousands of columns and would pretend position means the same thing in every record.
  • A literal dot or backslash inside a JSON key is escaped with a backslash before path segments are joined, so a key named profile.city cannot collide with the nested path profile then city.
  • Formula protection applies only to JSON strings and column names. A typed JSON number such as -2 stays -2; the string "-2" is prefixed because a spreadsheet decides cell type from the CSV text.
  • The UTF-8 marker is enabled by default for older Excel versions. It adds the three bytes EF BB BF and no row or column data; other spreadsheet and database tools normally tolerate it.
  • Comma and semicolon output use the .csv extension. A tab separator uses .tsv and the text/tab-separated-values MIME type rather than labelling tabular data as comma-separated.
  • Input is capped at 20 MB, at 500,000 rows and 5,000 columns. The rendered text is capped at 64 million characters so one expansion cannot exhaust the tab's memory.

Common questions

Is the JSON file uploaded before it becomes CSV?

No. A worker in this browser tab decodes the file, parses the JSON, builds the rows and encodes the download. The browser test for this page loads a real fixture and fails if any request leaves the site while the file is present.

What happens when later JSON objects have different keys?

Every key gets one column in the order it first appears. Earlier or later records that do not carry that key get an empty cell, so values never slide into the wrong heading merely because one object omitted a field.

Why is there an apostrophe before some spreadsheet cells?

Text beginning with =, +, - or @ can be interpreted as a formula when a CSV is opened in spreadsheet software. Formula protection prefixes those strings with an apostrophe so untrusted exported data remains text. Turn it off only when the formulas are deliberate and the source is trusted.

Why does flattening leave a JSON array in one cell?

An object's key is schema; an array's length and order are data. Expanding array positions would let one long list widen the whole file and would imply that item zero has the same meaning in every row. Keeping the array as JSON preserves it without inventing that claim.

Should I choose comma, semicolon or tab?

Comma is the interoperable CSV default. Semicolon is useful in locales where the comma is a decimal mark and spreadsheet software expects a semicolon list separator. Tab is best when the text contains many commas and produces an honestly named TSV file.

What is the Excel UTF-8 marker and do I need it?

It is the three-byte sequence EF BB BF at the beginning of the file. It helps older Excel versions recognise UTF-8 without an import dialog, so names and non-Latin scripts open correctly. Modern data tools usually do not need it, and the advanced toggle can omit it.

Sources