CSV ↔ JSON Converter

RFC 4180 has three quoting rules and split(",") breaks all three. A field can contain the delimiter, a field can contain a line break, and a quote inside a quoted field is written twice. This uses a character-by-character state machine, so "Springfield, IL" stays one cell and "he said ""no""" comes back with its quotes intact.

CSV to JSON Converter — RFC 4180 Quoting, Tabs and Semicolons, Both DirectionsBuildFigure

The three quoting rules

CSV looks like it can be split on commas until a field contains one. RFC 4180 settles it in three rules: a field containing the delimiter, a line break or a double quote is wrapped in double quotes; a double quote inside such a field is written twice; and a line break inside a quoted field belongs to the value rather than ending the record. The parser here is a state machine that tracks whether it is inside quotes, so all three hold. "Springfield, IL" is one field. "he said ""no""" unquotes to he said "no". A quoted address spanning two lines stays one record.

The one input it cannot fully rescue is a file where quoting is inconsistent — some fields quoted, one containing a stray unmatched quote. You get a warning that a quoted field was never closed, and everything after that quote lands in one enormous value. That is the correct reading of the bytes; the file is wrong.

Delimiter detection and how it fails

Detection counts commas, tabs, semicolons and pipes across the first five lines and takes the winner. Spreadsheet copy-paste comes through as tab-separated, so pasting cells straight out of Excel, Numbers or Google Sheets works with no configuration. Exports from a machine set to a European locale usually come through as semicolon-separated, because the comma is busy being the decimal separator there.

The counting is naive — it does not skip quoted regions — so a comma-delimited file whose free-text column is full of semicolons can be detected wrong. The tell is the ragged-row warning: if it says a lot of rows have a different field count, suspect the delimiter before you suspect the data, and set it explicitly.

Number coercion is optional for a reason

With coercion on, 30 becomes the number 30 and true, false and null become their JSON types. Two categories are held back deliberately. Anything with a leading zero stays a string, because 00123 as a number is 123 and the code is destroyed. Anything longer than fifteen digits stays a string, because past 253 a JavaScript number cannot hold the digits and a card number or a Snowflake ID would come back subtly altered.

Those two guards do not cover everything. A phone number stored without its leading zero, a part number that happens to be all digits, a year, a postal code in a country that has no leading zeros — all of these become numbers and all of them are identifiers, not quantities. If the table is mostly codes, turn coercion off and cast the handful of genuine numbers yourself.

Going to CSV: how the columns get chosen

For an array of objects, every key seen in any element becomes a column, in first-appearance order, and elements missing a key get an empty cell. For an array of arrays, rows are written through as-is with no header. If you hand it an object rather than an array it looks for the first array-valued property and uses that, which is what you want for an API response shaped like {"data": [...]}.

Nested values have nowhere to go in a flat table, so an object or array inside a cell is serialised to JSON text and stored in that one cell. Excel will show it as a wall of braces. If the nested fields matter, flatten first — either transform the JSON before converting, or convert twice and join.

Excel, encodings, and the mojibake

Output uses CRLF line endings, which is what the spec asks for and what every spreadsheet accepts. The thing that actually goes wrong when you open a CSV in Excel is the encoding: Excel guesses, and on a Windows machine it guesses the legacy code page rather than UTF-8, so accented characters and anything non-Latin turn to garbage. This is not a property of the file. Either save it with a UTF-8 byte-order mark, which is the signal Excel looks for, or use Data → From Text/CSV and pick the encoding in the import dialog. Google Sheets and LibreOffice assume UTF-8 and just work.

As with everything on this site, the conversion happens in your browser. Nothing is uploaded, which is the point when the table is a customer export.

Questions people ask

Can I paste cells straight from a spreadsheet?

Yes. Copying a range puts tab-separated text on the clipboard, and detection picks that up without you changing the delimiter. Cells containing line breaks are quoted by the spreadsheet on the way out, so they survive the round trip. The one thing to watch is that what you paste is the displayed value, not the underlying one: a date shown as 3/14 pastes as the string "3/14", and a number formatted to two decimals loses the rest.

Two of my columns have the same name.

The second gets _2 appended, the third _3, and so on, because JSON object keys have to be unique and the later one would otherwise overwrite the earlier one silently. Empty header cells become col1, col2 numbered by position. If you would rather keep the original headers exactly, turn off the header option and you get an array of arrays with the first row included as data.

How large a file can it handle?

Tens of thousands of rows is fine. The slow part is usually pasting into the textarea rather than the parse itself, and the output textarea has to hold the whole result as a string. Once you are into tens of megabytes, use something that streams: Python's csv module, Miller, or jq for the reverse direction. Those also avoid loading everything into memory twice.

Why did my leading zeros survive but my phone number turn into a number?

The guard is a leading zero, not the concept of an identifier. 00123 keeps its zeros; 5551234567 has no leading zero and is under sixteen digits, so it is coerced. There is no reliable way to tell an identifier from a quantity by looking at the digits. When a table is mostly codes, switch coercion off — everything comes through as a string and you convert the columns you actually want to do arithmetic on.

Related