By Docify

Convert JSON to CSV in JavaScript: Papa.unparse vs Papa.parse

• 6 min read

Papa.unparse writes CSV from JavaScript objects. Papa.parse reads CSV into objects. They are not inverses once numbers, nested values, or typing are involved. The live JSON to CSV page runs JSON.parse then Papa.unparse. For example, a JSON array with name, age, email, and city columns produces CSV like name,age,email,city then John Doe,30,john@example.com,New York (CRLF). Download output is converted.csv as text/csv. Nested objects and arrays are JSON.stringify'd so a cell is not [object Object]: Ada plus {city:NY} writes Ada,"{""city"":""NY""}". JSON.parse("1e16") is 10000000000000000; unparse writes that number. 1e-324 writes 0. 9007199254740993 writes 9007199254740992. On CSV to JSON, a two-column cell 1e16,foo stays the string "1e16" because Papa's FLOAT test requires parseFloat < 2^53. That page calls Papa.parse, not unparse. Not Excel, not a flattener, not NDJSON. Nothing is uploaded.

Papa Parse exposes two different functions. parse turns delimited text into rows. unparse turns rows into delimited text. Docify splits those APIs across two pages so each output is honest. This guide names what JSON to CSV writes after JSON.parse, why a nested value becomes a quoted JSON string, and why the same digit sequence is a number on one page and a string on the other.

Two Papa APIs, two pages

unparse writes CSV; parse reads it

Papa's docs treat unparse as the writer for an array of objects (or an array of arrays). Default newline is CRLF. Default delimiter is comma. The live JSON page passes columns as the first-seen union of Object.keys, so a later extra key still becomes a header. [{"name":"Ada","age":30},{"name":"Bob","city":"NY"}] writes name,age,city then Ada,30, and Bob,,NY. A missing later key is an empty field: Ada,30 then Bob,. null is an empty field. true / false write true / false. A string with a comma, quote, or LF is RFC 4180 quoted; an inner quote is doubled (hel"lo → "hel""lo").

Ada,30 + Bob,city NY → name,age,city then Ada,30, and Bob,,NY

Conversion happens on click

On the JSON to CSV page, paste or type JSON into the input box and click Convert. The page calls JSON.parse followed by Papa.unparse to produce CSV with CRLF line endings. Download saves the result as converted.csv with MIME type text/csv. You can also upload a .json or .txt file, which reads the file with FileReader.readAsText and fills the JSON input. Nothing is uploaded to a server.

Nested cells are JSON strings

Raw unparse writes [object Object]

CSV has no native nested object type. Papa.unparse stringifies each cell using the host ToString method, so an object would become the unhelpful string [object Object]. To preserve nested data, the JSON to CSV page runs JSON.stringify on any non-null object or array before calling unparse. This keeps the nested structure as valid JSON text within the cell. For example, a row with name "Ada" and a nested {city:"NY"} object produces Ada,"{""city"":""NY""}" — the inner quotes are doubled per RFC 4180. If you later parse that CSV back with Papa.parse, the cell comes back as a string, not a nested object.

raw unparse → Ada,[object Object] · this page → Ada,"{""city"":""NY""}"

IEEE-754 happens before unparse

1e16 is already 10000000000000000

JSON.parse uses IEEE-754 binary64 (ECMA-262 / RFC 8259 §6). Exact integers end at 9007199254740991 (2^53 − 1). JSON.parse("1e16") is 10000000000000000; Papa.unparse writes that decimal. JSON.parse("1e-324") is 0 (underflow). JSON.parse("9007199254740993") is 9007199254740992. Quoted "9007199254740993" stays the exact string. This is not BigInt.

1e16 → 10000000000000000 · 1e-324 → 0 · 9007199254740993 → 9007199254740992

Papa.parse keeps 1e16 as a string

The reverse page, /csv-to-json/, calls Papa.parse with header, skipEmptyLines, and dynamicTyping. Papa's FLOAT test requires parseFloat to be strictly inside ±2^53. A two-column output n,x plus 1e16,foo stays {"n":"1e16","x":"foo"}. Write 1e15 for the number 1000000000000000. A cell 9007199254740992 stays a string; 9007199254740991 becomes a number. 1e-324 becomes 0 on both pages (underflow is inside ±2^53). The two outputs are not a round trip.

What Convert rejects

One object is a row; NDJSON is not

After JSON.parse, an array of objects is kept. One object is wrapped as a one-row array: {"name":"Ada","age":30} writes name,age then Ada,30. [] and {} write an empty string (no header). Array of primitives such as [1,2,3], array of arrays, and JSON Lines / NDJSON fail. JSON.parse rejects a second value after the first object. Duplicate names keep the last parse value (RFC 8259 §4). Papa's unparse can write an array of arrays; this page does not accept that shape. Not Excel.

Convert objects with Papa.unparse

Example: Convert returns the four-column John Doe table. Nested values stay JSON strings. Nothing is uploaded.

Use JSON to CSV →

FAQ

What is the difference between Papa.parse and Papa.unparse?
Papa.parse converts CSV to JavaScript objects. Papa.unparse converts objects back to CSV.
Does Papa.parse auto-detect delimiters?
Yes. It detects commas, semicolons, tabs, and other common delimiters automatically.
How are headers handled?
With header: true, the first row becomes object keys. Otherwise rows are arrays.
Does this tool upload my data?
No. All parsing runs in your browser using Papa Parse. Nothing is sent to a server.

Related