JSON to CSV

Turn a JSON export into a spreadsheet-ready table, nested keys and all.

Your inputs stay on your deviceFREE · NO SIGN-UP
Nested objects
Lists inside a record
Field separator

Result

Download CSV

Start typing and the result appears here. No button needed.

THE LITTLE DETAILS

JSON to CSV, without the extra steps.

Turn an array of JSON records into a table a spreadsheet can open. The rows are found for you — a bare array, or the array inside the {"data": […]} wrapper almost every API answers with — and the columns are the union of every record's keys, so a record missing a key gets an empty cell instead of a shifted row. Nested objects become their own dotted columns, lists inside a record are joined, numbered, or left as JSON, and every column found is listed with how many records actually carried it.

How to use this tool

  1. 1Paste the JSON, or open a .json file. The table appears as you paste.
  2. 2Choose what a nested object and a list inside a record become, and the separator your spreadsheet expects.
  3. 3Check the columns it found, then download the CSV or copy it into an open sheet.

When JSON to CSV is the right tool

  • An API response is copied out of the browser's network tab and has to reach a colleague who works in a spreadsheet and does not read JSON.
  • An export from an admin panel is the usual {"data": […], "meta": {…}} shape, so the rows are one level down and a converter that only takes a bare array refuses it.
  • Each record carries a nested address object, and the sheet needs city and postcode as columns that can be sorted and filtered.
  • A log file arrives as JSON Lines — one object per line, no brackets, no commas — which is not a valid JSON document at all.
  • The file is going to a colleague running Excel in Germany, where the field separator is a semicolon and the names have accents in them.

Finding the rows is most of the job

A bare array of records is the easy case and the rare one. Real exports wrap the rows in an object beside a paging block, an errors array, or a count, so this searches the document for the array of records rather than refusing anything that is not a list. It takes the array of objects nearest the top, and the longest one where a level holds several, then says on screen which key it used — because picking the wrong array quietly is far worse than asking. A document that holds no such array is treated as one record, which is what a config file is.

A missing key is an empty cell, never a shifted row

Columns are the union of every record's keys, kept in the order they were first seen. A record that lacks a key gets an empty cell in that position, so column three is the same field on every row of the file. This is the failure that makes a converted export useless without being obviously broken: sort the sheet, and rows whose values slid left by one are indistinguishable from rows that are simply different. Every column is listed with how many records carried it, and the ones only some records had are marked.

Nested objects and lists are choices, not defaults

There is no single right answer for {"address": {"city": "Leeds"}} or for ["admin", "billing"], so both are controls. A nested object either spreads into dotted columns — address.city — or keeps its JSON in one cell, which is what you want for a settings blob you are only carrying across. A list joins with a semicolon and a space, expands into numbered columns, or stays as JSON. A list of objects cannot be joined into anything a spreadsheet can read, so it keeps its JSON whatever the setting, and the control says how many did.

Written to be opened, not just to be read

The result is a real file: CRLF line endings as RFC 4180 specifies, quoting only where the format forces it, internal quotes doubled, and a UTF-8 byte-order mark added when — and only when — the text contains something outside ASCII, which is what stops Excel on Windows turning Müller into Müller. It is named after the key the rows came from, so an export of orders downloads as orders.csv. Cells starting with =, +, - or @ are counted and flagged, because a spreadsheet runs those on open however they are quoted.

GOOD TO KNOW

A few quick answers.

Yes, and that is the common case. A document such as {"data": […], "meta": {…}} is searched for the array of records inside it, and the workspace says which key the rows came from so you can tell whether it picked the right one. If the document is a single object rather than a list, it becomes one header row and one record.

By default each leaf becomes its own column, named with a dot: address.city, address.postcode. That is what makes the result usable in a spreadsheet, because a cell holding {"city":…} cannot be sorted or filtered. Choose JSON in the cell when the nesting is data you want to keep whole — a settings blob, say — rather than columns you want to work with.

No. The columns are the union of every record's keys in the order they were first seen, and a record missing one gets an empty cell. That is the difference between a table and a mess: a value never slides into the next column. The workspace counts how many records were missing something, and marks any column that only some records carried.

Yes. If the whole paste is not valid JSON, each line is tried on its own; when every line parses and at least one of them is an object, it is read as one record per line and the workspace says so. That is the shape a log export, a BigQuery extract, or anything written a line at a time arrives in, and it is the input people are most often told is invalid JSON — which it is, as a document.

It becomes an empty cell. There is no way for a CSV to distinguish an empty string, a null, and a key that was not there, and writing the word null would put four letters into a column of numbers. The workspace counts nulls separately from missing keys so you can see which you have before you send the file, and both come out blank.

Not here — every column is written, in the order the keys were first seen. Once you have the CSV, Extract text columns takes the ones you want by number and in any order, including swapping them, which keeps that decision in one tool rather than two. For a wide export it is usually faster to hide columns in the spreadsheet after opening it.

The text does, but the parsing does not always. Values pass through JavaScript's number type, so an integer beyond about 9 quadrillion or a decimal with many significant digits can come back with different digits than the file had. If your records carry long numeric IDs, check whether the export quotes them as strings; if it does, they are safe, and if it does not, they were already at risk anywhere else that reads the JSON.

Back to all tools