Skip to Content
RowSlice

Free Online JSON to CSV Converter

Convert a JSON Array or NDJSON Export to CSV

An API gave you a few hundred megabytes of JSON and the person who needs it works in a spreadsheet. This reads the document element by element rather than parsing it whole, flattens nested keys into dotted column names, and writes one CSV.

  • Multi-GB files
  • Nested keys flattened
  • Never uploaded

In short: RowSlice's JSON to CSV converter reads a JSON array of objects, or NDJSON, and writes one CSV row per object — in your browser, with nothing uploaded. Nested objects become dotted column names such as customer.address.city. Arrays stay in a single cell as JSON text rather than becoming columns. The header is the union of the keys across every row, so records that do not all carry the same fields still line up. Anything in the file that is not an object is counted and reported, and the run carries on. The document is never handed to a parser whole, so a file far larger than memory converts without the tab falling over.

How to Convert JSON to CSV

  1. Choose or drag in the .json or .ndjson file.
  2. Leave What the file holds on Detect automatically unless the file is unusual. The first character that is not whitespace decides it, and the next section shows what each one looks like.
  3. Leave Flatten nested objects ticked to get dotted column names. Untick it to keep each nested object whole, in one cell, as JSON.
  4. Press Convert to CSV. It reads the file twice — once to work out the columns, once to write the rows — and the progress bar covers both passes.
  5. Download yourfile.csv. The result gives the number of columns it settled on and the number of elements it had to skip, which is the first thing to check when the row count is not what you expected.

The Two Shapes It Accepts

A top-level array of objects:

[
  {"id": 1, "status": "open"},
  {"id": 2, "status": "closed"}
]

or NDJSON, the same records with one object per line and no punctuation holding them together:

{"id": 1, "status": "open"}
{"id": 2, "status": "closed"}

Both give the same two-row CSV. The detection is one rule: the first character in the file that is not a space or a line break. An opening square bracket means the array; anything else means one object per line. There is a setting to say which it is, for the rare file that guesses wrong.

For the array shape, indentation is irrelevant — the file is cut into elements by counting brackets and braces, not by reading lines, and a brace inside a quoted value does not count. A pretty-printed array whose objects each span ten lines reads exactly like a compact one. For NDJSON the line is the record, so a blank line is skipped as a separator rather than counted as a broken row, and a Windows line ending is handled.

What Each JSON Value Becomes in the CSV

Every value in the document lands in the CSV in one of these ways. The sections after the table work through the two that surprise people most, nested objects and arrays.

In the JSONIn the CSVWhy
A string or an ordinary number, "city": "Leeds"The text the file holds, in a column called cityValues are copied rather than interpreted: no date is reformatted and no number is rounded on purpose
A nested object, "customer" holding "address" holding "city"One column per leaf, named for the path that reaches it: customer.address.cityFlattening goes 20 levels deep; a branch deeper than that goes into one cell as JSON text
An array, "tags": ["vip","trade"]One cell holding ["vip","trade"] as JSON textSpreading an array across columns would let whichever record has the most entries set the width of the table
"note": nullAn empty cellA CSV has no way to write null that a reader would not take as the four-letter word
A key that only some records carryA column of its own, empty in the records that do not carry itThe header is the union of the keys found across the whole file, up to 1,024 columns
true and falsetrue and falseWritten as the words JSON uses for them, in lower case
A number longer than 15 significant digits, such as a 19-digit idEvery digit in Chrome and Edge; the rounded double in FirefoxA JSON number is an IEEE 754 double, and Chrome 114 and later also hand the reader the number's original text
An element that is not a JSON object: a bare string, a number, a broken lineNo row at all; it is counted as skippedOne bad line out of a million must not end the run, so the count and the first five are reported instead

How Nested Objects Become Columns

A CSV has one level. JSON usually has several, so every leaf gets a column named for the path that reaches it, joined with dots. Two records from an order export, with the second missing a postcode:

{"id":1,"customer":{"name":"Ann Poole","address":{"city":"Leeds","postcode":"LS1 4AP"}},"note":null}
{"id":2,"customer":{"name":"Bo Chen","address":{"city":"Derby"}},"note":"call back"}

come out as five columns:

id,customer.name,customer.address.city,customer.address.postcode,note
1,Ann Poole,Leeds,LS1 4AP,
2,Bo Chen,Derby,,call back

Three things happened there worth naming. The header is the union of the keys found across the whole file, not the keys of the first record, which is why customer.address.postcode is a column even though only one row fills it. Columns appear in the order they are first seen. And "note": null became an empty cell, the same cell a key that is absent altogether gets — a CSV has no way to write null that a reader would not take as the word.

Two limits apply, and both are reported rather than passed over. Flattening stops at 20 levels; past that the rest of the branch goes into a single cell as JSON, because a twenty-segment path is not a heading anyone reads. And the header stops at 1,024 columns: the list of keys is the one thing here that grows with the data, and a spreadsheet a thousand columns wide is past being useful. Keys past the cap are left out and the result says so. Untick Flatten nested objects and none of this happens — each top-level key gets one column, and anything nested under it sits in the cell as JSON.

What Happens to Arrays

An array is kept in one cell, written as JSON text. It is the choice that surprises people most, so here is the reasoning, with three records where the array has two entries, none, and one:

{"sku":"A-77","tags":["vip","trade"]}
{"sku":"B-12","tags":[]}
{"sku":"C-03","tags":["clearance"]}
sku,tags
A-77,"[""vip"",""trade""]"
B-12,[]
C-03,"[""clearance""]"

The alternative is to spread an array across columns — tags.0, tags.1, and so on. It looks tidy on the first three rows and falls apart on a real file. The width of the table would be set by whichever single record happens to carry the most entries, so one product with forty tags gives every other row thirty-nine empty cells; the header could not be written until the last record had been read; and the column a value lands in would depend on its position in an array, which in most exports means nothing at all. Keeping the array intact is lossless, the width is stable, and any consumer can parse the cell back.

The doubled quotation marks in the output are not damage — they are how CSV quotes a value that contains a comma and a quote. A CSV reader gives you ["vip","trade"] back. An empty array needs no quoting at all, which is why B-12's cell is a bare []. The number of cells written this way is counted with the result, which is a useful signal: a high count means the export is not really a table yet.

Rows It Has to Skip

Real exports have a broken line in them — a truncated download, a log file with a stray message in it, a hand-edited record. One bad line out of a million must not end the run, so anything that is not a JSON object is counted and the conversion continues. This NDJSON has an unclosed brace on line 2 and a bare string on line 4:

{"id":1,"status":"open"}
{"id":2,"status":"closed"
{"id":3,"status":"open"}
"just a string"
{"id":4,"status":"open"}

and converts to three rows:

id,status
1,open
3,open
4,open

The result reports 2 skipped and shows both, each with the line number it was on and the start of its text, cut to 200 characters and put on one line so that a huge element cannot flood the report. At most the first five are shown, however many there are. Three reasons are distinguished: not valid JSON, valid but not an object, and too deeply nested to read — enough to tell a truncated file apart from a file of the wrong shape.

The one case that stops the run is a file where nothing is an object, because there are no rows to write and an empty CSV would be a worse answer than a message. Then it says how many elements it read and shows the first, which is usually all you need to see what the file really is. If you would rather check the CSV afterwards than trust the counts, the validator reads the written file and reports its rows, quoting and encoding.

How Big a JSON File Can It Convert?

This is where most JSON-to-CSV converters give up, and it is worth being plain about why. The obvious implementation reads the file into a string and calls a JSON parser on it. That parser builds the whole document as objects in memory before it returns anything, and an object graph costs more than the text it was built from, so a 2 GB export takes the tab with it long before the first row is written.

Nothing here is parsed whole. The file is read about 4 MB at a time and cut into top-level elements by counting brackets and braces as the bytes arrive — tracking whether the scanner is inside a quoted string, so the brace in {"note":"{not a brace}"} does not confuse it. Each element is parsed on its own and thrown away once its cells are written. What stays in memory is one element and one slot per column, never one per row.

The file is read twice, and the reason is worth knowing because it shows up in the timing: a CSV header has to be written before the first row, and which columns exist is only known once every record has been read. The first pass collects the keys, the second writes the rows. Holding the rows between the two would be faster and is the one thing this engine will not do. The progress bar covers both passes, so a run that reaches halfway has read the file once.

The result goes to your browser's private on-disk storage (OPFS) as it is produced, inside the allowance your browser gives one site — about 10 GB in Firefox. The CSV is usually much smaller than the JSON, because the keys are written once in the header instead of on every record. Afterwards, the analyzer will tell you what you have ended up with — row count, column profiles, the range of the values — without opening the file in anything, and the viewer pages through the rows themselves when the CSV is still too big for a spreadsheet. Two pulls from the same endpoint, converted the same way, line up as well: matching the two CSVs on an id column reports which records were added, removed and changed between the two pulls.

What It Does Not Do

It does not pick a branch out of a document. There is no path expression and no convert the array under this key option: the tool reads a top-level array of objects, or one object per line, and nothing else. A response wrapped in {"data": [ … ], "meta": { … }} has to be unwrapped first.

It does not guess types. Values go into the CSV as the text the file holds, and a CSV carries no types anyway. Nothing is reformatted, no date is reinterpreted, no number is rounded on purpose — the one thing the browser can impose is the double at the heart of every JSON number, which is why the FAQ above is specific about long identifiers. What you get is a faithful flattening, not a cleaned-up one.

It reads JSON, not JavaScript. Comments, trailing commas, single-quoted keys and unquoted keys are not JSON, and a record containing them is counted as skipped rather than repaired.

It writes CSV, not a workbook. One file in, one file out. For an .xlsx that keeps leading zeros and long identifiers intact, convert this CSV again with the CSV to Excel converter, or follow the guide to importing it by hand if you want to choose each column's type yourself. To go back the other way, the CSV to JSON converter writes an array or NDJSON from a CSV.

Last updated .

FAQ

Frequently Asked Questions

Which JSON does it read, and how does it decide?

Two shapes: a top-level array of objects, and NDJSON — one object per line, with no brackets and no commas between them. The first character in the file that is not a space or a line break decides which: an opening square bracket means the array, anything else means one object per line. You can also set the shape yourself if a file is unusual. Whitespace and indentation make no difference to the array, which is read as a stream of elements rather than line by line, so a pretty-printed array with every object across ten lines reads exactly the same as a compact one.

My file is one big object with the records under a "data" key. Will it work?

Not as it stands, and it is worth knowing what happens instead of guessing. {"data": [ … ]} does not start with a bracket, so it is read as one object per line. Written on a single line it parses as one object and you get a one-row CSV with a single column, data, holding the whole array as JSON text. Pretty-printed across many lines, no line is valid JSON on its own, every line is reported as skipped and the run stops with a message rather than writing an empty file. Either way the fix is the same: take the array out of its wrapper first — jq '.data' export.json > rows.json does it — and convert that.

How does a nested object become columns?

Each leaf gets a column named for the path to it, joined with dots: customer.address.city. The columns are the union of the keys found across every row, in the order they are first seen, so a key that only some rows have still gets a column and the rows without it get an empty cell. Flattening goes 20 levels deep; past that the rest of the branch goes into one cell as JSON text, because a path twenty segments long is not a column heading anyone can read. Turn flattening off and every nested object goes into one cell as JSON instead, which is the right choice when you want the structure kept rather than spread out.

Why is my tags array one cell of JSON instead of several columns?

Because exploding it would make the header depend on the data. If the first row has three tags and row 40,000 has forty, the table needs forty columns that almost every row leaves empty, and you cannot know that until the file has been read to the end. So an array goes into one cell as JSON text — ["vip","trade"] — which a spreadsheet shows as-is and any code can parse back in one line. The cells written that way are counted with the result, so you know how much of the file is not really tabular. If you need one row per tag, that is a reshape of the JSON before conversion, not something a CSV writer can decide for you.

What happens to null, to true, and to a key a row does not have?

A missing key and a null both become an empty cell, because a CSV has no way to write null that a reader would not take as the four-letter word. true and false are written as true and false. Numbers and strings go in as the text the file holds. If empty and null need to stay distinct in the result, that is a distinction CSV cannot carry, and the honest answer is to keep the JSON.

Will a 19-digit ID keep all its digits?

In Chrome and Edge, yes. A JSON number is a double, the same as a cell in Excel, so past 15 significant digits the value an ordinary JSON reader hands back is already rounded. Chrome 114 and later also give the reader the original text of each number, and the converter writes that text into the cell, so the digits your file holds are the digits in the CSV. Firefox does not offer that yet, so there a 19-digit id is written as JavaScript prints the double — rounded, the same loss Excel is better known for. Strings are never touched in any browser, so an id quoted in the JSON survives everywhere.

Some of my lines are broken. Does the whole run fail?

No. An element that is not valid JSON, or that is valid but is not an object — a bare string, a number, an array — is counted and the run carries on. The result gives the total and the first five, each with the line it was on and the start of the text, so you can go and look at them. The run only stops when nothing in the file is an object, since there would be no rows to write, and then the message says how many elements were read and shows the first. A deliberately mixed file is fine: put an array of objects through with a few stray values in it and you get a CSV of the objects and a count of the rest.

What does the CSV look like — encoding, delimiter, line endings?

It is a new file rather than a copy of one, so it is written the way the program it is heading for wants it: UTF-8 with a byte order mark, CRLF line endings and a comma delimiter by default. The mark is what stops Excel turning café into café; untick it for a tool that shows it as stray characters in front of the first column name. The delimiter can be a semicolon, tab or pipe instead, and values are quoted only when they need it — when they contain the delimiter, a quotation mark or a line break. CSV UTF-8 vs CSV in Excel explains which of Excel’s CSV formats matches.

How big can the JSON be, and is it uploaded?

Nothing is uploaded: the conversion runs in your browser, on your own computer, and the page works offline once it has loaded. The file is read about 4 MB at a time and never handed to a JSON parser whole, so memory does not grow with the document and there is no row limit — what limits the job is free disk space for the result, inside the allowance your browser gives one site, about 10 GB in Firefox. The CSV is normally a good deal smaller than the JSON it came from, because the keys are written once as a header instead of on every record. One ceiling exists: a single element longer than 67,108,864 characters stops the run, which in practice means a quotation mark that is never closed. Tested in current Chrome, Edge and Firefox on Windows; Safari and macOS are untested.

More Tools

More CSV Tools

CSV Analyzer

Count the rows, list the columns and profile the values in a CSV too big to open.

CSV Viewer

Page through and search a CSV that is far too large for Excel, without uploading it.

Compare Two CSV Files

See what was added, removed and changed between two exports, matched on an ID column.

Take a Sample from a CSV

Pull the first rows, every Nth row or a random slice out of a huge file for testing.

Sample CSV File Generator

Download realistic test CSV files from 10 rows up to 20 GB, with messy values on request.

Split CSV into Multiple Files

Cut a CSV that Excel refuses to open into multiple files by rows, size or number of parts.

Split CSV by Column Value

One file for every store, region, client or status — straight from a huge export.

Filter CSV Rows

Keep only the rows you need — by value, text match or number range — from a file of any size.

Merge CSV Files

Combine multiple CSV files into one, lining up columns by name, even when the result is too big for Excel.

Remove Duplicates from CSV

Delete repeated rows from millions of records, comparing whole rows or chosen columns.

Convert CSV to Excel

Make a real .xlsx file without losing leading zeros, long IDs or turning codes into dates.

Convert CSV to JSON

Turn a CSV into a JSON array or NDJSON, with every value kept exactly as written.

CSV Validator

Find the broken rows, bad quotes, wrong encoding and duplicate IDs before an import fails.

Convert CSV to UTF-8

Repair garbled characters like é by converting to UTF-8, and switch semicolons to commas.