JSON to CSV Converter: Export JSON Data as CSV Free
Key Takeaways
- ▸Flat JSON arrays convert to CSV trivially — the hard problem is nested objects and arrays of objects, which require explicit flattening strategies.
- ▸In 2024, over 90% of web APIs exchange data in JSON or CSV — making conversion between the two formats a near-daily developer task.
- ▸Dot notation (
user.address.city) is the most universally understood flattening convention — it round-trips cleanly and is readable by non-developers in spreadsheets. - ▸Jackson (Java) hits 1 GB/s parse throughput in 2025 benchmarks — for high-volume server-side conversion pipelines, library choice matters more than algorithm choice.
- ▸For gigabyte-scale JSON files, streaming with
jqor Python'sijsonavoids loading the full document into memory — critical for datasets that exceed available RAM.
The Myth: "CSV Is Just Simpler JSON"
Here is a misconception that trips up developers every time: CSV is not a subset of JSON, and converting between the two is not always lossless. JSON models a tree. CSV models a table. Trees have arbitrary depth and branching; tables have exactly two dimensions — rows and columns. This fundamental structural mismatch is the root of every non-obvious behavior you encounter when converting JSON to CSV.
Consider what happens to this JSON object when you naively try to map it to CSV columns:
{
"id": 1,
"user": {
"name": "Alice Chen",
"address": {
"city": "San Francisco",
"zip": "94105"
}
},
"tags": ["backend", "api"],
"orders": [
{ "id": "o1", "total": 49.99 },
{ "id": "o2", "total": 129.00 }
]
}You have three problems: a nested user object (two levels deep), a primitive array (tags), and an array of objects (orders) that fundamentally cannot map to a single row without a deliberate design decision. How you resolve each of these determines your CSV schema — and there is no universally correct answer. There are only trade-offs.
In 2024, more than 90% of web APIs exchanged data in either JSON or CSV, according to toolsana.com's data format adoption research. JSON won the API battle; CSV never left the data analysis world. Developers are perpetually converting between the two, and understanding the trade-offs is the difference between a usable CSV export and a broken one.
Three Flattening Strategies — and When to Use Each
There is no single correct way to flatten JSON. The right strategy depends on your audience (developers vs. analysts), your downstream tooling (SQL vs. Excel vs. pandas), and the expected depth of your data.
Strategy 1: Dot Notation (Recommended Default)
Nested keys are joined with dots. user.address.city becomes a column header. This is the most readable convention for both developers and analysts. It preserves the original key hierarchy, making it obvious where each value came from, and round-trips cleanly back to JSON if needed.
function flatten(obj, prefix = '', sep = '.') {
return Object.keys(obj).reduce((acc, key) => {
const fullKey = prefix ? `${prefix}${sep}${key}` : key
const val = obj[key]
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
Object.assign(acc, flatten(val, fullKey, sep))
} else {
acc[fullKey] = val
}
return acc
}, {})
}
flatten({ user: { address: { city: 'SF', zip: '94105' } } })
// → { 'user.address.city': 'SF', 'user.address.zip': '94105' }Strategy 2: Underscore Notation for SQL Targets
When the CSV is destined for import into a SQL database, dots in column names create quoting requirements in most dialects. Use underscores instead — user_address_city maps directly to a valid SQL column name without escaping. Change the sep parameter in the function above to '_'.
Strategy 3: JSON-Stringify Nested Values (Escape Hatch)
When nested objects are too deep or too irregular to flatten usefully, serialize them as JSON strings inside the CSV cell. This is an escape hatch — the resulting CSV is technically valid but requires the consumer to parse JSON cells, which defeats some of the purpose of CSV. Use it only when the nested structure varies per-record (sparse, schema-less documents) where flattening would create hundreds of mostly-empty columns.
function flattenWithDepthLimit(obj, prefix = '', maxDepth = 2, depth = 0) {
if (depth >= maxDepth) {
return { [prefix]: JSON.stringify(obj) }
}
return Object.keys(obj).reduce((acc, key) => {
const fullKey = prefix ? `${prefix}.${key}` : key
const val = obj[key]
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
Object.assign(acc, flattenWithDepthLimit(val, fullKey, maxDepth, depth + 1))
} else {
acc[fullKey] = val
}
return acc
}, {})
}The Array Problem: Primitives vs. Objects
Arrays are where JSON-to-CSV conversion gets genuinely hard. Two distinct cases require different treatment:
Arrays of Primitives
A field like "tags": ["backend", "api", "rest"] cannot map to a single typed CSV column. The two common approaches:
- Join with pipe:
backend|api|rest— preserves all data in one cell, readable but requires parsing on consumption. - Expand to boolean columns:
tags_backend=1, tags_api=1, tags_rest=0— one-hot encoding, great for ML pipelines, terrible for open-ended tag values.
Pipe joining is the default in most converters. It is the right choice when you don't know the consumer in advance.
Arrays of Objects (One-to-Many Relationships)
This is the genuinely hard case. A user record with two orders cannot fit on one CSV row without losing data integrity. Your options:
| Strategy | Output | Best for | Risk |
|---|---|---|---|
| Row explosion | One row per order; parent fields repeated | SQL import, pivot tables | Row count multiplies; aggregation breaks |
| JSON stringify | Array stored as JSON string in one cell | Quick exports, data engineers | Cell requires further parsing |
| Columnar expand | orders[0].id, orders[1].id … columns | Fixed-size arrays, report generation | Breaks for variable-length arrays |
| Separate files | users.csv + orders.csv with FK column | Relational export, database import | Multiple files complicate delivery |
Row explosion is correct when the nested objects are the primary entity of interest (you want one row per order, not one row per user). JSON stringify is the right pragmatic default for one-off exports. For anything that will run regularly, the separate files approach — essentially normalizing your JSON into a relational model — is the most robust.
RFC 4180: The CSV Spec Nobody Reads
CSV has a formal specification — RFC 4180, published by the IETF in 2005 — and most developers have never read it. This is why CSV files generated by different tools often fail to parse correctly. The critical rules:
- Line endings are CRLF (
\r\n). RFC 4180 §2.1 mandates carriage return + line feed. Many parsers accept LF alone for compatibility, but generating CRLF output is technically correct. - Fields containing commas, double-quotes, or newlines must be quoted. Wrap the field value in double-quotes. A double-quote inside a quoted field is escaped by doubling it (
""), not with a backslash. - The header row is optional. RFC 4180 §2.3 says a header line may appear as the first record. Most tooling produces headers by default — when converting JSON objects, the keys become headers.
- All records must have the same number of fields. If your JSON objects have inconsistent keys, union them and fill missing values with empty strings.
function serializeCSV(rows) {
if (!rows.length) return ''
// Union all keys across all records
const headers = [...new Set(rows.flatMap(Object.keys))]
const escape = (val) => {
if (val === null || val === undefined) return ''
const str = String(val)
// Quote if contains comma, double-quote, newline, or CR
if (/[,"
]/.test(str)) return '"' + str.replace(/"/g, '""') + '"'
return str
}
const lines = [
headers.map(escape).join(','),
...rows.map(row => headers.map(h => escape(row[h])).join(','))
]
return lines.join('
') // RFC 4180 §2.1: CRLF line endings
}Note the double-quote escaping on line 12 — str.replace(/"/g, '""'). This is the most common bug in hand-rolled CSV serializers. Backslash escaping (\") is not valid per RFC 4180 but is accepted by many parsers, which means broken files silently parse in your environment but fail for the person on the other side of the file transfer.
JSON to CSV in Every Major Language
The right implementation depends on scale. For one-off conversions and quick scripts, reach for a high-level library. For production pipelines processing millions of records, library and parser choice measurably affects throughput.
Python: pandas for the 80% Case
For flat JSON arrays, pandas handles conversion in two lines. For nested structures, json_normalize() provides programmatic flattening control.
import pandas as pd
import json
# Flat JSON array → CSV (the easy case)
with open('data.json') as f:
data = json.load(f)
df = pd.DataFrame(data)
df.to_csv('output.csv', index=False)
# Nested JSON with json_normalize (the real case)
from pandas import json_normalize
nested_data = [
{"id": 1, "user": {"name": "Alice", "city": "SF"}, "score": 99},
{"id": 2, "user": {"name": "Bob", "city": "NY"}, "score": 87},
]
df = json_normalize(nested_data)
# Produces columns: id, user.name, user.city, score
df.to_csv('normalized.csv', index=False)
# One-to-many: expand orders array into separate rows
orders_data = [{"user_id": 1, "orders": [{"id": "o1"}, {"id": "o2"}]}]
df = json_normalize(orders_data, record_path='orders', meta=['user_id'])
df.to_csv('orders.csv', index=False)JavaScript / Node.js: csv42 for Nested Data
PapaParse is the dominant JS CSV library (GitHub: mholt/PapaParse) but has a known limitation: it does not natively handle nested JSON objects. The csv42 library by Jos de Jong specifically targets nested JSON with a getter/setter architecture that avoids creating intermediate flattened objects entirely — more memory-efficient for large datasets.
// npm install csv42
import { toCSV } from 'csv42'
const data = [
{ id: 1, user: { name: 'Alice', city: 'SF' }, tags: ['api', 'backend'] },
{ id: 2, user: { name: 'Bob', city: 'NY' }, tags: ['frontend'] },
]
const csv = toCSV(data, {
fields: [
{ name: 'id' },
{ name: 'user.name', getValue: (item) => item.user.name },
{ name: 'user.city', getValue: (item) => item.user.city },
{ name: 'tags', getValue: (item) => item.tags.join('|') },
]
})
// id,user.name,user.city,tags
// 1,Alice,SF,api|backend
// 2,Bob,NY,frontendCommand-Line: jq for Streaming Large Files
For files too large to load into memory, jq is unbeatable. It streams JSON without building an in-memory document tree, making it practical for gigabyte-scale datasets.
# Flat array → CSV with headers jq -r '(.[0] | keys_unsorted) as $keys | $keys, (.[] | [.[$keys[]]] | @csv)' data.json # Nested object: extract specific fields jq -r '.[] | [.id, .user.name, .user.city] | @csv' data.json # Stream from large file (avoids loading full document) jq -cn --stream ' fromstream(1|truncate_stream(inputs)) | [.id, .name] | @csv ' large-file.json
The @csv filter in jq handles RFC 4180 quoting correctly, including double-quote escaping and comma handling.
Performance at Scale: Library Benchmarks
For one-off conversions, any library works. For pipelines processing millions of records per hour, parser throughput matters. Per 2025 benchmarks from the devpik.com JSON-to-CSV conversion guide:
| Library / Tool | Language | Throughput | Nested JSON | Streaming |
|---|---|---|---|---|
| Jackson + csv-schema | Java | ~1 GB/s | Yes (with module) | Yes |
| jq | CLI / C | ~500 MB/s | Yes (manual) | Yes (--stream) |
| pandas json_normalize | Python | ~100 MB/s | Yes (built-in) | No (chunksize workaround) |
| csv42 | JavaScript | ~80 MB/s | Yes (native) | No |
| PapaParse (unparse) | JavaScript | ~40 MB/s | No (manual flatten required) | Partial |
Jackson's 1 GB/s throughput (per apidog.com's 2025 benchmarks) makes it the choice for high-volume Java batch pipelines. For Node.js microservices, csv42's native nested object support is worth the lower raw throughput relative to hand-rolling a flatten + PapaParse combination — and it eliminates the intermediate flattened object allocation, reducing GC pressure.
Online JSON to CSV Converters: What to Look For
If you need a quick one-off conversion, an online tool is the fastest path. But not all online converters are equal. Here is what separates production-quality tools from throwaway ones:
- Client-side processing: Verify no data leaves your browser (DevTools → Network panel while pasting). This is non-negotiable for any data that could be sensitive.
- Nested object support: Most basic converters only handle flat arrays. A quality converter handles at least 2–3 levels of nesting with configurable flattening strategy.
- Array handling options: Can you choose between pipe-joining primitive arrays vs. JSON-stringifying them?
- Custom delimiter: Tab-separated (TSV) output is often more useful for Excel imports than comma-separated, because Excel auto-detects TSV cleanly without import wizard dialogs.
- Download without Excel conversion: The file should download as
.csv, not as an XLSX — XLSX conversion adds format complexity that a CSV converter has no business doing.
BytePane's JSON Formatter handles JSON validation and pretty-printing entirely client-side — the same privacy architecture applies to our conversion tools.
Six JSON-to-CSV Errors Developers Hit Every Time
1. Input is not a JSON Array
CSV requires tabular data — rows and columns. If your top-level JSON is an object rather than an array, you have one row of data. Either wrap it in an array ([data]) or use jq to extract the array: jq '.results' api-response.json | jq -r '.[0] | keys, (.[] | [.[]] | @csv)'.
2. Inconsistent Object Keys Across Records
If record 1 has { "name": "Alice", "age": 30 } and record 2 has { "name": "Bob", "email": "[email protected]" }, a naive converter uses only the first record's keys as headers, silently dropping email from the output. The fix: union all keys across all records before generating headers.
3. Values Containing Commas
The value "San Francisco, CA" contains a comma. Without RFC 4180-compliant quoting ("San Francisco, CA"), it splits into two columns. Every hand-rolled serializer gets this wrong at least once.
4. Numbers That Are Too Large
JSON numbers outside IEEE 754 double-precision range (larger than 253 − 1) lose precision in JavaScript's JSON.parse(). A Snowflake row ID like 9007199254740993 becomes 9007199254740992 — silently wrong. Use Python or Java (which preserve big integers) for pipelines that handle financial IDs or database PKs from 64-bit systems. For more on this JSON numeric edge case, see our JSON formatting guide.
5. Unicode Characters Breaking Excel
Excel (on Windows) expects CSV files to begin with a UTF-8 BOM (0xEF 0xBB 0xBF) to correctly detect UTF-8 encoding. Without it, Japanese, Arabic, or emoji content renders as garbage. Add the BOM when targeting Excel: '\uFEFF' + csvString. Mac Excel and Google Sheets detect UTF-8 without the BOM.
6. Null vs. Empty String
JSON null and "" (empty string) are semantically different. In CSV, both typically appear as empty cells, making them indistinguishable to the consumer. If nulls are meaningful in your data model, either add a sentinel value, use a separate boolean column, or switch to a format that natively supports nullability — Parquet or Avro are common choices for data pipelines where null semantics matter.
Frequently Asked Questions
How do I convert nested JSON to CSV?
Nested JSON must be flattened before it maps to CSV rows. The standard approach uses dot notation — {"user": {"name": "Alice"}} becomes a column named user.name. Arrays of primitives are typically joined with a pipe delimiter. Arrays of objects require a decision: expand into multiple rows or stringify the nested array as a JSON string.
What happens to JSON arrays when converting to CSV?
Arrays of primitives (strings, numbers) are usually joined into a single cell with a pipe or comma delimiter. Arrays of objects each become their own row, with the parent record's scalar fields repeated on each row. Most converters let you choose the behavior — the right choice depends on your downstream tooling.
Is it safe to use an online JSON to CSV converter for sensitive data?
Only if the tool runs entirely client-side in your browser. Verify by opening DevTools → Network panel and confirming no outbound requests fire when you paste your JSON. BytePane's converter performs all conversion in your browser using JavaScript — no data is sent to any server.
Can I convert JSON to CSV in Python?
Yes. For flat JSON arrays, pd.DataFrame(data).to_csv() handles it in two lines. For nested structures, use json_normalize() from pandas with record_path and meta arguments. For deeply nested data without pandas, write a recursive flatten function and pass the results to the csv module's DictWriter.
Why does my JSON to CSV conversion produce extra empty columns?
This happens when objects in your JSON array have inconsistent keys — some records have fields others are missing. A correct converter unions all keys across all objects and fills missing values with empty strings. Dozens of spurious empty columns usually indicate sparse or optional fields that vary across records.
How large a JSON file can I convert to CSV online?
Browser-based converters typically handle up to 50–100 MB before memory pressure causes slowdowns. For larger files, use jq on the command line — it streams data without loading the full document into memory. Python with ijson or pandas chunksize is another good option for multi-gigabyte datasets.
What is the difference between CSV and TSV?
CSV (Comma-Separated Values) uses commas as delimiters and double-quotes to escape commas in field values. TSV (Tab-Separated Values) uses tab characters as delimiters. TSV is simpler to produce because tabs rarely appear in data, eliminating most quoting edge cases. CSV has broader spreadsheet application support.
Convert JSON to CSV Instantly
Paste your JSON array and download a clean, RFC 4180-compliant CSV. Runs entirely in your browser — no data leaves your machine.
Open JSON Formatter & Tools →