JSON vs CSV comes down to shape. CSV is a flat grid of rows and columns. JSON is a nested tree of objects, arrays and typed values. Both are common for moving data between systems, and that difference in shape is why converting between them is harder than it looks.
This guide compares the two, shows when each one fits, and covers the hard part: flattening nested JSON into CSV columns and getting types back on the return trip. To just convert a file, use the JSON to CSV and CSV to JSON converters.
JSON vs CSV at a glance
Both formats are plain text that people can read, but they answer different questions. CSV answers "what does this table look like?" JSON answers "what is the shape of this object?" Pick the wrong one and you fight the format for as long as the file exists.
| Aspect | CSV | JSON |
|---|---|---|
| Structure | Flat rows and columns | Nested objects and arrays |
| Types | None, everything is text | string, number, boolean, null, object, array |
| Repetition | Compact, no repeated keys | Keys repeat on every object |
| Nesting | Not supported | Built in |
| Tooling | Excel, Sheets, pandas, SQL imports | APIs, config, JS and most languages |
| Streaming | Easy, one line at a time | Needs a streaming parser or NDJSON |
A simple rule: if a non-developer will open the file, use CSV. If a program will read it over HTTP, use JSON. For the same comparison against other formats, see JSON vs YAML and JSON vs XML.
When to use CSV
CSV works best when your data is truly tabular: a list of records where every row has the same fields. Spreadsheets and analytics tools all read it. For large flat datasets it is much smaller than JSON, because column names appear once in the header instead of on every record.
- Exports meant to open in Excel or Google Sheets
- Bulk imports into a SQL database or data warehouse
- Analytics and data-science pipelines (pandas, R, DuckDB)
- Large uniform datasets where per-row key repetition wastes space
- Handing data to non-technical stakeholders
CSV struggles as soon as your data has optional fields, nested structures or arrays. You end up with sparse columns, made-up naming schemes, or cells packed with delimiters.
When to use JSON
Use JSON when structure and types matter. It keeps the difference between the number 42 and the string "42", between false and "false", and between a missing field and an empty one. It handles nesting and arrays without encoding tricks, which is why web APIs and config files use it.
- REST and GraphQL request and response bodies
- Configuration files and application state
- Documents with nested or variable structure
- Data where types must survive a round trip
- Anything read directly by JavaScript or most modern languages
The hard part: flattening nested JSON
CSV has no nesting, so converting JSON to CSV means fitting a tree onto a grid. The usual approach joins nested keys into one column name with a path convention. Dot notation is the most common. Take this nested record:
[
{
"id": 1,
"name": "Ada Lovelace",
"active": true,
"address": { "city": "London", "zip": "WC1" },
"tags": ["admin", "beta"]
}
]With dot notation for objects and index notation for the array, it becomes a single row. The nested address.city key and each tag position get their own column:
id,name,active,address.city,address.zip,tags.0,tags.1
1,Ada Lovelace,true,London,WC1,admin,betaHandling arrays
Arrays are the hardest part, because their length varies between records. You have three options, each with a trade-off:
- Index into columns (tags.0, tags.1). This is simple, but the column set changes with array length, so the output gets sparse or ragged.
- Join into one cell (tags = "admin;beta"). The column set stays fixed, but you must pick a separator that will not collide with your delimiter, and you lose the array structure.
- Explode into multiple rows, one per array element, repeating the parent fields. Analysts prefer this tidy-data layout, but it changes the row count and repeats data.
Converting CSV back to JSON and inferring types
The reverse direction has the opposite problem. CSV values are all strings, so a basic parser returns every field as text. Rebuilding JSON takes two jobs: rebuilding the nesting from the column names, and guessing the intended type of each value.
function inferType(value) {
if (value === "") return null
if (value === "true") return true
if (value === "false") return false
// Only treat as a number if it round-trips exactly,
// so "007" and "1e999" stay strings.
const num = Number(value)
if (!Number.isNaN(num) && String(num) === value) return num
return value
}
console.log(inferType("42")) // 42 (number)
console.log(inferType("007")) // "007" (string, leading zero preserved)
console.log(inferType("true")) // true (boolean)
console.log(inferType("")) // nullRebuilding the nesting reverses the flattening. Split each header on the dot, then walk the path and create objects as you go, or arrays for numeric segments.
function setPath(target, path, value) {
const keys = path.split(".")
let node = target
keys.forEach((key, i) => {
if (i === keys.length - 1) {
node[key] = value
return
}
// Next segment numeric? create an array, else an object.
if (node[key] === undefined) {
node[key] = /^\d+$/.test(keys[i + 1]) ? [] : {}
}
node = node[key]
})
return target
}
const row = { "address.city": "London", "tags.0": "admin" }
let out = {}
for (const [header, value] of Object.entries(row)) {
setPath(out, header, value)
}
// out => { address: { city: "London" }, tags: ["admin"] }Delimiters, headers and escaping
Most CSV problems in practice are not about structure. They come from the mechanics of the format. The common standard is RFC 4180, and following it prevents most import failures.
Delimiters
The C in CSV means comma. Semicolons are common in locales where the comma is the decimal separator, and tabs (TSV) avoid the problem altogether. Whatever you pick, the writer and the parser must agree. A file written with semicolons but read as comma-delimited collapses into one column.
Headers
The first row is usually a header that names each column. The spec makes it optional, but JSON conversion needs it, because the header supplies the object keys. Keep headers stable and unique. Duplicate column names make the target object ambiguous.
Quoting and escaping
Wrap any field that contains the delimiter, a double quote or a line break in double quotes. Escape a double quote inside the field by doubling it. People get this rule wrong by hand more than any other:
id,note
1,"Says ""hi"", then, a pause
and a newline"A practical conversion workflow
Put together, a reliable round trip looks like this:
- Check the JSON shape first. A flat array of uniform objects converts cleanly. Nested data needs a flattening decision.
- Choose an array strategy up front (index, join or explode) and use it across the whole dataset.
- Pick a delimiter that will not collide with your data, and always write a header row.
- Convert, then spot-check identifier columns for unwanted type changes, such as lost leading zeros or long IDs read as numbers.
- If the receiving system is strict, validate the rebuilt JSON against a schema.
Both converters run in your browser, so nothing is uploaded. Use JSON to CSV for exports. It flattens nested objects into dot-path columns and quotes values per RFC 4180. Use CSV to JSON for imports. It can infer numbers and booleans, and it keeps dot-path headers as flat keys, so rebuild the nesting in code if you need it.