CSV to JSON in the Browser with Papa Parse
To convert CSV to JSON in Docify, paste a table (or read a local .csv / .txt file) and click Convert. Papa Parse runs with header: true, skipEmptyLines: true, and dynamicTyping: true. The result is an array of objects, pretty-printed with 2-space JSON.stringify. A padded ID such as 007 becomes the number 7 (parseFloat). Quoted CSV "007" is still 7. A plus-prefixed cell such as +30 stays the string "+30" (Papa FLOAT accepts an optional minus, not a plus). -30 becomes -30. Plus is only legal in an exponent (1e+2). An incomplete exponent such as 1e or 1e+ stays the string "1e" or "1e+" (Papa FLOAT needs one or more digits after e or E). Write 1e+2 or 100 for the number. A hexadecimal prefix such as 0x1 stays the string "0x1" (Papa FLOAT has no 0x). Write 1 for the number. An underscore separator such as 1_000 stays the string "1_000" (Papa FLOAT has no underscore). Write 1000 for the number. A leading or trailing decimal such as .5 or 1. becomes 0.5 or 1 (Papa FLOAT allows those forms; JSON.parse rejects them). Quoted ".5" is still 0.5. A cell at ±2^53 such as 9007199254740992 stays the string "9007199254740992" (Papa testFloat is exclusive: floatValue > MIN_FLOAT && floatValue < MAX_FLOAT). 9007199254740991 becomes a number. A complete exponent such as 1e16 stays the string "1e16" because parseFloat of "1e16" is 10000000000000000, which is not < 2^53. Write 1e15 for the number 1000000000000000. A complete tiny exponent such as 1e-324 becomes the number 0 because parseFloat of "1e-324" is 0 (IEEE-754 underflow; Number.MIN_VALUE is 5e-324), and 0 is inside ±2^53. Write 5e-324 for Number.MIN_VALUE. A FLOAT cell with surrounding space such as 30 becomes 30 (FLOAT regex /^\s*…\s*$/). Quoted " 30 " is still 30. true stays a string. An unquoted both-side tab around 30 on a one-column header is auto-detected as a delimiter (TooManyFields); the page writes no JSON. A quoted tab-padded 30 stays one cell and becomes 30 (FLOAT \s includes tab). In a two-column comma table, an unquoted tab-padded 30 is also 30. A one-column table such as name then Ada is UndetectableDelimiter (average field count is 1, below 1.99) . Papa still defaults to comma and would return [{"name":"Ada"}], but Convert treats any result.errors as fatal and writes no JSON. Two-column n,x plus 30,foo succeeds. Duplicate headers are renamed so object keys are not overwritten: the first name stays name; a later name becomes name_1. name,name plus Ada,Bob becomes {"name":"Ada","name_1":"Bob"} and Convert writes that JSON. Quoted "name" / "name" is the same. name and Name are different keys. JSON.parse of {"name":"Ada","name":"Bob"} keeps the last value (RFC 8259 §4). A short data row such as name,age,city plus Ada,30 is TooFewFields (expected 3 fields but parsed 2). Papa still builds {"name":"Ada","age":30} and omits city, but Convert treats that FieldMismatch as fatal and writes no JSON. A trailing comma (Ada,30,) is three fields ({"name":"Ada","age":30,"city":null}) and Convert succeeds. A long row such as name,age plus Ada,30,extra is TooManyFields; Papa stores the extra cell on __parsed_extra as ["extra"]. An unclosed quote such as name,note plus Ada,"hello is MissingQuotes (Quoted field unterminated). Papa still builds {"name":"Ada","note":"hello"}, but Convert treats that Quotes error as fatal and writes no JSON. A closed Ada,"hello" succeeds. A trailing space after a closing quote such as Ada,"hello" plus a space is InvalidQuotes (Trailing quote on quoted field is malformed) plus MissingQuotes. Papa still builds {"name":"Ada","note":"hello\" "}, but Convert treats those Quotes errors as fatal and writes no JSON. A doubled quote inside a quoted field such as Ada,"hel""lo" is RFC 4180 §7 (the "" pair is one quote, ABNF 2DQUOTE). Convert writes {"name":"Ada","note":"hel\"lo"} and there is no Quotes error. A two-column semicolon table such as name;age then Ada;30 is auto-detected (guessDelimiter keeps a candidate when the average field count is greater than 1.99). Convert writes [{"name":"Ada","age":30}]. Papa's FLOAT test uses a decimal point, so 3,14 in that table stays the string "3,14". Write 3.14 for the number 3.14. Mixed comma and semicolon in the same table is UndetectableDelimiter; Convert writes no JSON. A full ISO timestamp such as 2026-09-18T12:00:00Z becomes 2026-09-18T12:00:00.000Z (Papa ISO_DATE, then Date.prototype.toJSON). A date-only cell stays a string. The strings true and TRUE become JSON true; title-case True stays a string. An empty cell, and a quoted empty "", become JSON null. A space-only cell and the word null stay strings. A blank line between Ada,30 and Bob,40 is skipped (skipEmptyLines: true); Convert writes both rows. A spaces-only row is UndetectableDelimiter plus TooFewFields; Convert writes no JSON. A comma-only row , is {"name":null,"age":null} and Convert succeeds. A two-column hash line such as # note,ignored is kept as data (comments defaults to false). Convert writes {"name":"# note","age":"ignored"} plus the Ada row. A one-field # comment row is UndetectableDelimiter plus TooFewFields; Convert writes no JSON. Nothing is uploaded.
Conversion is a button, not a live preview. The CSV to JSON converter does not parse as you type, does not read Excel workbooks, and does not turn JSON back into CSV. For how each option maps a row to an object, see how CSV rows become JSON objects.
Buttons on the page
- Convert — disabled while the CSV box is empty. Runs
Papa.parseon that string. If the box is only whitespace, the page shows “Please enter CSV data” and does not write JSON. - Upload CSV — accepts
.csvor.txtand fills the CSV box withFileReader.readAsText. It does not callPapa.parse(file)and does not run Convert for you. - Download JSON — disabled until there is output. Saves the pretty-printed string as
application/json(converted.json). - Load sample — fills the four-column sample table. It does not click Convert.
- Clear — empties CSV, JSON, and the error. Disabled only when both boxes are already empty.
Sample table after Convert
Load sample, then Convert. Ages become numbers because of dynamicTyping. The old “instant convert” example that showed "age": "30" was wrong for this page.
[
{
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"city": "New York"
},
{
"name": "Jane Smith",
"age": 25,
"email": "jane@example.com",
"city": "Los Angeles"
},
{
"name": "Bob Johnson",
"age": 35,
"email": "bob@example.com",
"city": "Chicago"
}
]Copy vs download
The JSON card has a Copy button that writes the same string to the clipboard. Both Copy and Download stay disabled until Convert succeeds. Pretty-print later in the JSON formatter if you need to minify or re-indent — this page always uses 2-space JSON.stringify.
Convert a CSV table
Paste or read a local file, click Convert, copy or download the objects. No upload.
Use CSV to JSON →FAQ
- Does converting CSV to JSON upload my table?
- No. Paste, Upload CSV, Convert, Copy, Download JSON, Load sample, and Clear all run in your browser. The page does not send the CSV or JSON to a server.
- Does the JSON update as I type?
- No. You click Convert. The button stays disabled while the CSV box is empty. Load sample fills the sample table but does not run Convert. Clear empties the CSV box, the JSON box, and any error.
- What does Load sample put in the box?
- A four-column header (name, age, email, city) and three rows: John Doe / 30 / john@example.com / New York, Jane Smith / 25 / jane@example.com / Los Angeles, and Bob Johnson / 35 / bob@example.com / Chicago.
- Why is age a number in the sample JSON, not a string?
- Convert calls Papa.parse with dynamicTyping: true. Decimal-looking values strictly inside ±2^53 become numbers, so 30 is 30, not "30". A padded ID such as 007 is also a decimal literal: parseFloat("007") is 7, so zip,007 becomes {"zip":7}. Quoted CSV "007" is still 7. 007a stays a string. A plus-prefixed cell such as +30 stays the string "+30" (Papa’s FLOAT test accepts an optional minus, not a plus). -30 becomes -30. Plus is only legal in an exponent (1e+2). An incomplete exponent such as 1e or 1e+ stays the string "1e" or "1e+" (FLOAT needs one or more digits after e or E). Write 1e+2 for the number 100. A hexadecimal prefix such as 0x1 stays the string "0x1" (FLOAT has no 0x). Write 1 for the number. A complete exponent such as 1e16 stays the string "1e16" (parseFloat is 1e16, which is not < 2^53). Write 1e15 for the number 1000000000000000. A complete tiny exponent such as 1e-324 becomes 0 (parseFloat underflows; 0 is inside ±2^53). Write 5e-324 for Number.MIN_VALUE. Quoted "0x1" is still "0x1". An underscore separator such as 1_000 stays the string "1_000" (FLOAT has no _). Write 1000 for the number. Quoted "1_000" is still "1_000". A leading or trailing decimal such as .5 or 1. becomes 0.5 or 1 (FLOAT allows those forms; JSON.parse rejects them). Quoted ".5" is still 0.5. The range check is exclusive (floatValue > MIN_FLOAT && floatValue < MAX_FLOAT, MAX_FLOAT = 2^53), so 9007199254740992 stays the string "9007199254740992" and 9007199254740991 becomes a number. Surrounding whitespace on a FLOAT cell is allowed (/^\s*…\s*$/), so 30 becomes 30. Quoted " 30 " is still 30. true stays the string " true " (boolean is exact equality). An unquoted both-side tab around 30 on a one-column header is auto-detected as a delimiter (TooManyFields; this page writes no JSON). A quoted tab-padded 30 stays one cell and becomes 30 because \s includes tab. In a two-column comma table, an unquoted tab-padded 30 is also 30 because comma already won. A clean one-column table such as name / Ada is UndetectableDelimiter (average field count is 1, below 1.99); Papa still builds [{"name":"Ada"}], but Convert treats that warning as fatal and writes no JSON. Two-column n,x plus 30,foo succeeds. Duplicate headers are renamed so keys are not overwritten: name,name plus Ada,Bob becomes {"name":"Ada","name_1":"Bob"} and Convert writes that JSON. A short data row such as name,age,city plus Ada,30 is TooFewFields (expected 3 fields but parsed 2); Papa still builds {"name":"Ada","age":30} and omits city, but Convert treats that FieldMismatch as fatal and writes no JSON. A trailing comma (Ada,30,) is three fields ({"name":"Ada","age":30,"city":null}) and Convert succeeds. A long row such as name,age plus Ada,30,extra is TooManyFields; Papa stores the extra cell on __parsed_extra as ["extra"]. An unclosed quote such as name,note plus Ada,"hello is MissingQuotes ("Quoted field unterminated"); Papa still builds {"name":"Ada","note":"hello"}, but Convert treats that Quotes error as fatal and writes no JSON. A closed Ada,"hello" succeeds. A trailing space after a closing quote such as Ada,"hello" plus a space is InvalidQuotes ("Trailing quote on quoted field is malformed") plus MissingQuotes; Papa still builds {"name":"Ada","note":"hello\" "}, but Convert treats those Quotes errors as fatal and writes no JSON. A doubled quote inside a quoted field such as Ada,"hel""lo" is RFC 4180 §7 (2DQUOTE); Convert writes {"name":"Ada","note":"hel\"lo"} and there is no Quotes error. A two-column semicolon table such as name;age plus Ada;30 is auto-detected (average field count 2, above 1.99); Convert writes [{"name":"Ada","age":30}]. Papa’s FLOAT test uses a decimal point, so n;x plus 3,14;foo stays {"n":"3,14","x":"foo"}. Write 3.14 for the number 3.14. Mixed comma and semicolon (name,age plus Ada;30) is UndetectableDelimiter; Convert writes no JSON. A blank line between Ada,30 and Bob,40 is skipped (skipEmptyLines: true; testEmptyLine is a single empty field); Convert writes both rows. A spaces-only row is UndetectableDelimiter plus TooFewFields; Convert writes no JSON. A comma-only row , is {"name":null,"age":null} and Convert succeeds. A two-column hash line such as # note,ignored is kept as data (comments defaults to false); Convert writes {"name":"# note","age":"ignored"} plus the Ada row. A one-field # comment row is UndetectableDelimiter plus TooFewFields. A full ISO timestamp such as 2026-09-18T12:00:00Z becomes a Date, then JSON.stringify writes 2026-09-18T12:00:00.000Z (Date.prototype.toJSON). A date-only cell such as 2026-09-18 stays a string. An empty cell, and a quoted empty "", become JSON null (parseDynamic returns value === "" ? null : value). A space-only cell and the word null stay strings. Emails and city names stay strings. European numbers that still use a comma as the decimal mark stay strings.