JSON and CSV are the two formats you reach for most when moving data between systems, but they model data in fundamentally different ways. CSV is a flat grid of rows and columns; JSON is a nested tree of objects, arrays, and typed values. That mismatch is exactly why converting between them is trickier than it looks.
This guide compares the two head to head, shows when each is the right tool, and walks through the hard part: flattening nested JSON into CSV columns and inferring types on the way back. If you just want the result, the JSON to CSV and CSV to JSON converters handle the edge cases for you.
JSON vs CSV at a Glance
Both formats are plain text and human-readable, 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 the life of the file.
| 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 natively | First-class |
| Tooling | Excel, Sheets, pandas, SQL imports | APIs, config, JS/most languages |
| Streaming | Line-by-line is trivial | Needs a streaming parser or NDJSON |
A useful rule of thumb: if a non-developer will open the file, lean CSV; if a program will consume it over HTTP, lean JSON. For similar breakdowns against other formats, see JSON vs YAML and JSON vs XML.
When to Use CSV
CSV shines when your data is genuinely tabular — a uniform list of records where every row has the same fields. It is the lingua franca of spreadsheets and analytics tools, and it is far more compact than JSON for large flat datasets because column names are written once in the header instead of repeating 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
The catch: the moment your data has optional fields, nested structures, or arrays, CSV starts to strain. You end up with sparse columns, invented naming conventions, or values stuffed with delimiters.
When to Use JSON
JSON is the right choice when structure and types matter. It preserves the difference between the number 42 and the string "42", between false and "false", and between a missing field and an empty one. It represents nesting and arrays without encoding tricks, which is why it dominates web APIs and configuration.
- 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 consumed directly by JavaScript or most modern languages
The Core Challenge: Flattening Nested JSON
CSV has no concept of nesting, so converting JSON to CSV means projecting a tree onto a grid. The standard approach flattens nested keys into a single column name using a path convention — dot notation is the most common. Consider this nested record:
[
{
"id": 1,
"name": "Ada Lovelace",
"active": true,
"address": { "city": "London", "zip": "WC1" },
"tags": ["admin", "beta"]
}
]Flattened with dot notation for objects and index notation for the array, it becomes a single row. The nested address.city key and the two tag positions each 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 practical options, each with a trade-off:
- Index into columns (tags.0, tags.1) — simple, but the column set changes when array length changes, producing sparse or ragged output.
- Join into one cell (tags = "admin;beta") — keeps a fixed column set, but you must pick a separator that won't collide with your delimiter, and you lose the array structure.
- Explode into multiple rows — one row per array element, duplicating the parent fields. This is the tidy-data approach analysts prefer, but it changes the row count and repeats data.
Converting CSV Back to JSON and Inferring Types
Going the other way has the opposite problem: CSV values are all strings, so a naive parser gives you every field as text. Rebuilding JSON means two jobs — reconstructing nesting from the column names, and inferring 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("")) // nullReconstructing nesting is the reverse of flattening: split each header on the dot, then walk the path, creating objects (or arrays for numeric segments) as you go.
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 real-world CSV pain is not about structure — it is about the mechanics of the format. The de facto standard is RFC 4180, and following it prevents the majority of import failures.
Delimiters
The C in CSV means comma, but semicolons are common in locales where the comma is the decimal separator, and tabs (TSV) sidestep the problem entirely. Whatever you pick, the writer and the parser must agree — a file written with semicolons but read as comma-delimited collapses into a single column.
Headers
The first row is conventionally the header naming each column. It is optional in the spec but effectively required for JSON conversion, since the header supplies the object keys. Keep headers stable and unique — duplicate column names make the target object ambiguous.
Quoting and escaping
Any field containing the delimiter, a double quote, or a line break must be wrapped in double quotes, and literal double quotes inside are escaped by doubling them. This is the rule people most often get wrong by hand:
id,note
1,"Says ""hi"", then, a pause
and a newline"A Practical Conversion Workflow
Putting it together, a reliable round-trip looks like this:
- Inspect the JSON shape first — is it a flat array of uniform objects, or deeply nested? Flat and uniform converts cleanly; nested needs a flattening decision.
- Choose an array strategy up front (index, join, or explode) and use it consistently across the dataset.
- Pick a delimiter that won't collide with your data, and always emit a header row.
- Convert, then spot-check identifier columns for unwanted type coercion (leading zeros, large numeric-looking IDs).
- Validate the reconstructed JSON against a schema if the downstream system is strict.
For quick, private conversions that never upload your data to a server, run both directions in the browser: JSON to CSV for exports and CSV to JSON for imports. Both handle quoting and dot-notation nesting so you don't have to hand-roll the edge cases.