Home / Guides / Convert a JSON file to CSV

Convert a JSON file to CSV

To convert a JSON file to CSV in the browser, open the Upload File tab, pick the file, and press Convert to CSV. A file takes one step a paste never takes — the browser has to decode it — and that step is where a file can go wrong without an error.

The short answer

Pick the file in the Upload File tab and press Convert to CSV. Every file up to 100 MB converted and stayed alive — 2.0 s to read plus 2.7 s to convert at 100 MB — and 150 MB and 200 MB converted too, then took the tab with them about 7 seconds later.

Convert a JSON file to CSV in the browser

Runs entirely in your browser. The file is never uploaded, so there is no account, no queue and no server-side size limit.

Two things to know before you upload anything large. A UTF-8 BOM, a UTF-16 file with a BOM, CRLF line endings, a missing final newline and a .txt or .jsonl extension all converted normally; a file saved in a legacy encoding did not fail, it converted and wrote replacement characters into the data, which is the one failure that looks like success. And the row count the tool prints next to the button is the check that matters: comparing it against your source takes two seconds.

records.json — 62 bytes, saved as Latin-1, not UTF-8
[{"id": 1, "city": "caf\xE9"}]
The CSV the tool printed — no error
id,city
1,caf\xEF\xBF\xBD

In Latin-1 the single byte \xE9 is é. The browser decodes the file as UTF-8, where a lone \xE9 is not a valid character, so it becomes U+FFFD — three bytes, \xEF\xBF\xBD, in the downloaded file. Nothing warns you. This is exactly the difference between a file and a paste: your clipboard already holds a character, and a file holds bytes that somebody has to interpret.

On this page
  1. What the file path does that a paste does not
  2. Seven sizes, measured
  3. Where the tab dies
  4. Twelve files, twelve results
  5. The same bytes through both doors
  6. A 60-second check before you trust the file
  7. When a browser converter is the wrong tool
  8. Questions people ask next

What the file path does that a paste does not

The converter behind this site is one file of pure functions with no DOM in it. Both doors into it end at the same two calls: parse the text, then write the CSV. A file adds one step in front:

  1. Read the bytes. The browser reads the file with FileReader.readAsText. If the file starts with a byte-order mark, that mark decides the encoding. If there is no mark, the browser assumes UTF-8 — and this assumption is silent, which is the whole problem with the case above.
  2. Decode to a string. What the converter receives is a JavaScript string, not bytes. From here on, a file and a paste are the same input.
  3. Parse and convert. A UTF-8 BOM survives the decode as a U+FEFF character, and the parser trims it, which is why a BOM'd file works rather than throwing Unexpected token.

Measured, that is the entire difference: the same 1 MB file through the Upload File tab and the same bytes pasted into the Paste tab produced a CSV with the same MD5, b76f5226330aba0695dbe7bf262c04c4, 3,419 rows and 14 columns both times. Everything after the decode is the same code on the same string.

The file path measured at 100 MB: a 104,857,903 byte file read in 2.0 seconds, growing the tab's heap by 98.7 MB, then parsed and converted in 2.7 seconds into 58,840,387 bytes of CSV. A second note records that at 150 MB the same pipeline finished and the tab died 7.6 seconds later.
One file, one tab, measured at 100 MB. The heap number is the tab's JavaScript heap as Chrome reports it, after the read and before the conversion; the file's text is essentially the whole of it, because the JSON is ASCII and stores one byte per character.

Seven sizes, measured

Seven files, generated by a seeded script so the bytes are reproducible, each one uploaded through the Upload File tab on the live page and converted with the Convert to CSV button. One record per line, twelve fields per record, including a nested object, an array and a note field that contains a comma and a double quote. Every size up to 100 MB was run 5 times and the column below is the median of those runs; 200 MB was run 3 times, 150 MB was run 2 times, because every one of those runs ends with a dead tab.

FileRecordsReadConvertCSV outCSV ÷ fileHeap after readResult
1.00 MB
1,048,777 B
3,41934 ms34 ms581,5910.555×5.7 MBConverted
5.00 MB
5,242,893 B
16,985172 ms174 ms2,921,8970.557×9.7 MBConverted
10.00 MB
10,485,923 B
33,883227 ms272 ms5,855,2990.558×14.7 MBConverted
50.00 MB
52,428,885 B
168,6251.1 s1.2 s29,383,8110.560×54.7 MBConverted
100.00 MB
104,857,903 B
336,7162.0 s2.7 s58,840,3870.561×103.4 MBConverted
150.00 MB
157,286,622 B
501,8463.7 s4.6 s88,700,4100.564×153.4 MBConverted, then the tab died 7.6 s later
200.00 MB
209,715,410 B
671,9295.0 s6.2 s117,884,8180.562×203.4 MBConverted, then the tab died 7 s later

Two numbers fall out of that table and hold at every size. The tab's JavaScript heap after the read, minus the 4.7 MB the page already used, came to 0.99–1.02× the file size — the JSON is ASCII, and a JavaScript string of ASCII stores one byte per character, so the file's text costs about what the file costs. And the CSV came out at 0.555–0.564× the input every time, because the columns are the same and the JSON punctuation is what disappears.

Add those two and you get the number that decides whether a file is safe to convert in a browser: the file's text and the CSV it produces are both live at the same moment, so a tab needs roughly 1.56× the file size, plus the parsed records and the garbage from building the string. That is a floor, not a peak. We measured the peak as far as it can be measured from outside — see the note under the next table.

Where the tab dies

The two largest files are in the table above because they converted: the tool reported the correct row count and produced the correct CSV. The tab then went away on its own, 7.6 seconds later at 150 MB and 7 seconds later at 200 MB — 5 runs at 200 MB and 150 MB, every one of which ended with a dead tab; the stopwatch was recorded in 2 of them. During the 200 MB run this machine's free physical memory fell from 1149 MB to 198 MB. The tab's own JavaScript heap cap was 2112 MiB and was never reached, so what ran out was the machine's memory, not the JavaScript heap.

What that means for you, stated carefully, because it is easy to over-read: the file size is not the limit. The limit is how much memory the machine has free at the moment you press Convert. On a machine with more RAM the same 200 MB file will very likely survive; on a laptop with a browser full of tabs and 1 GB free, a 150 MB file may not. The part that transfers is the ratio above, not the number.

What was measured and what was not. The read time, the convert time, the CSV byte count and the heap after the read are all measured on the live page and reproducible. The peak heap during the conversion is not: the conversion is one synchronous task, so nothing outside the tab can sample the heap while it runs. We checked that the metric we do publish behaves the way we describe it — a 20 MiB text string decoded from bytes showed up as exactly 20.00 MiB, and dropping the bytes while keeping the string left it at 20.00 MiB — and then published the two components of the peak (the file text and the CSV) rather than a made-up maximum. The metric behaved exactly as expected on a controlled pair: a 20 MiB typed array showed up as 20.00 MiB, decoding it to text added another 20.01 MiB, and dropping the bytes while keeping the string left 20.01 MiB, which is the text and nothing else.

We stopped at 200 MB on purpose. The next step up needs a file bigger than the machine's free memory, and the point was already made: past 100 MB you are converting on borrowed memory, and the failure mode is not a wrong CSV, it is a tab that closes. If your file is that big, use a tool that streams.

Twelve files, twelve results

Same converter, same page, same button — twelve files that differ only in how the bytes are arranged on disk. Every result below is the literal output the tool printed, or the exact error text it showed. \xNN is a raw byte.

FileBytesWhat the tool printedVerdict
records.json
64 B · 5B 0A 20 20 7B 22 69 64…
64id,city↵1,café↵2,ZürichReference
records-bom.json
67 B · EF BB BF 5B 0A 20 20 7B…
67id,city↵1,café↵2,ZürichBOM tolerated
records-utf16.json
126 B · FF FE 5B 00 0A 00 20 00…
126id,city↵1,café↵2,ZürichDecoded from the BOM
records-utf16be.json
126 B · FE FF 00 5B 00 0A 00 20…
126id,city↵1,café↵2,ZürichDecoded from the BOM
records-latin1.json
62 B · 5B 0A 20 20 7B 22 69 64…
62id,city↵1,caf�↵2,Z�richSilent data loss
records-crlf.json
67 B · 5B 0D 0A 20 20 7B 22 69…
67id,city↵1,café↵2,ZürichSame as reference
records-nonewline.json
64 B · 5B 0A 20 20 7B 22 69 64…
64id,city↵1,café↵2,ZürichSame as reference
records.txt
64 B · 5B 0A 20 20 7B 22 69 64…
64id,city↵1,café↵2,ZürichExtension not checked
records.jsonl
56 B · 7B 22 69 64 22 3A 20 31…
56id,city↵1,café↵2,ZürichRead as JSON Lines
one-object.json
26 B · 7B 22 69 64 22 3A 20 31…
26id,city↵1,caféOne row
empty.json
2 B · 5B 5D…
2No rows to convert. Provide an array of objects or a single object.Error, no file written
trailing-comma.json
29 B · 5B 7B 22 69 64 22 3A 20…
29Invalid JSON: Unexpected token ']', ...": "café"},]" is not valid JSONError, no file written

The first column of that table is the point of this page. Four of the twelve differ only in bytes that never reach the parser: a UTF-8 BOM, a UTF-16 BOM in either byte order, CRLF line endings, and a missing final newline. All four produced the same CSV as the plain file. Three more differ only in the filename: .txt and .jsonl are accepted, and a .jsonl file holding one object per line is read as JSON Lines rather than as a broken document. The extension is a hint to your file picker, not a rule the converter enforces.

Then there is the one that matters: a file with no BOM in an encoding that is not UTF-8. The converter cannot detect it, because it never sees the bytes. The browser decodes them as UTF-8, the invalid bytes become U+FFFD, and the CSV is written with the damage inside it. If your JSON came out of an older Windows tool, a database export or a spreadsheet, that is the case to check for.

The same bytes through both doors

A control measurement, because the claim above is worth nothing if the two doors behave differently for other reasons. The 1 MB fixture, once uploaded and once pasted as text:

DoorRecordsCSV bytesCSV MD5
File — Upload File tab3,419581,591b76f5226330aba0695dbe7bf262c04c4
Paste — Paste tab3,419581,591b76f5226330aba0695dbe7bf262c04c4

Identical. And a paste that begins with a U+FEFF character produces the same CSV as a paste without it, so the BOM handling is in the parser's trim and not in the file reader. The two doors differ in exactly one place, and it is the decode.

A 60-second check before you trust the file

  1. Compare the row count. The tool prints “N rows, M columns” next to the button. If your source has 12,000 records and it says 11,999, you have a broken line and you want to know now rather than after the import.
  2. Search the CSV for U+FFFD. One replacement character means the file was not UTF-8. Fix the encoding at the source — open it in your editor and save as UTF-8 — rather than fixing the CSV afterwards.
  3. Look at the header. If you left Flatten nested on, nested objects became dot-path columns such as address.city. If you turned it off, they stayed as JSON text inside one cell. Both are valid; only one is what your importer expects.
  4. Check the first data row for leading zeros and long ids. A CSV that is correct can still lose 007 or a 19-digit id when the spreadsheet opens it, because that is the spreadsheet's type inference, not the conversion.
  5. Check the file's own encoding before you upload. file -i records.json on macOS or Linux answers it in one line; on Windows, most editors show the encoding in the status bar. us-ascii and utf-8 are safe. Anything else needs converting first.

When a browser converter is the wrong tool

A browser converter is the right tool when the file fits in memory twice and you want to look at the result now: it needs no install, no account and no upload, and for anything under 100 MB it is faster than finding a terminal. It is the wrong tool in three cases:

For everything else — a 5 MB export, an API response you saved, a file a colleague sent — uploading it somewhere to be converted is a strange thing to do, and this site's converter is the same code as the one on the JSON to CSV page, running locally.

Questions people ask next

How do I convert a JSON file to CSV without uploading it?

Use a converter that runs in the page. This one reads the file with the browser's own file reader and converts it in your tab, so the bytes never leave your machine and there is no server-side size limit. The practical limit is your own free memory, which is measured below.

What is the largest JSON file a browser can convert to CSV?

On a machine with about 1.2 GB of free memory, every file up to 100 MB converted and stayed alive: 2.0 s to read and 2.7 s to convert at 100 MB. At 150 MB and 200 MB the conversion also finished and produced the correct CSV, and then the tab died about 7 seconds later. A tab holds the file's text and the CSV at the same time, so budget about 1.56 times the file size in free memory.

Why does my JSON file fail to parse when the text looks correct?

It is usually an encoding problem rather than a syntax problem. A UTF-8 BOM is fine, because the parser trims it, but a file saved in a legacy encoding such as Latin-1 is decoded as UTF-8, its invalid bytes become U+FFFD, and the parse can then fail on the replacement character. Convert the file to UTF-8 at the source and upload it again.

Does the converter accept a .txt or a .jsonl file?

Yes. Measured: the extension is not checked at all. A .jsonl file with one JSON object per line is read as JSON Lines and converts to the same CSV as the equivalent JSON array.

What happens if the file has a trailing comma?

The tool shows an Invalid JSON error naming the unexpected token and writes no file. Trailing commas are not valid JSON, in a file or in a paste, and no converter can guess where you meant the list to end.

What happens if the file contains an empty array?

It shows No rows to convert and writes no file, rather than downloading a zero-byte CSV. A zero-byte file would import as a silent success in most tools, so the error is the better outcome.

Is uploading a file different from pasting the same JSON?

Only in one place: the decode. Measured on the same 1 MB file, both doors produced a CSV with the same MD5 and the same row count. A file's bytes have to be decoded to text first, and that is where an encoding problem can enter; a paste is already text.