CSV remains the lingua franca of data exchange for spreadsheets, analytics tools, and business reporting. While JSON is the developer's format of choice, analysts, accountants, and managers need CSV. Whether you're exporting API data for Excel, preparing reports for stakeholders, or loading data into pandas DataFrames, knowing how to reliably convert JSON to CSV is an essential skill.
JSON supports nested objects, arrays, heterogeneous types, and null values. CSV is a flat grid of rows and columns with string values. Converting between them means resolving these structural mismatches. A simple JSON.stringify() won't work —you need a strategy for flattening nested structures, handling arrays, and escaping special characters.
Method 1: JavaScript —Manual Conversion
Basic Flat Object Conversion
// Input JSON array
const data = [
{ "name": "Alice", "email": "[email protected]", "role": "admin" },
{ "name": "Bob", "email": "[email protected]", "role": "user" },
{ "name": "Carol", "email": "[email protected]", "role": "editor" }
];
// Convert to CSV
function jsonToCsv(data) {
if (!data.length) return '';
const headers = Object.keys(data[0]);
const csvRows = [headers.join(',')];
for (const row of data) {
const values = headers.map(h => {
const val = row[h] ?? '';
return '"' + String(val).replace(/"/g, '""') + '"';
});
csvRows.push(values.join(','));
}
return csvRows.join('\n');
}
console.log(jsonToCsv(data));
// name,email,role
// "Alice","[email protected]","admin"
// "Bob","[email protected]","user"
// "Carol","[email protected]","editor"
Handling Nested Objects
// Flatten nested JSON objects for CSV
function flattenObject(obj, prefix = '') {
const result = {};
for (const key of Object.keys(obj)) {
const newKey = prefix ? prefix + '.' + key : key;
const value = obj[key];
if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.assign(result, flattenObject(value, newKey));
} else {
result[newKey] = value;
}
}
return result;
}
// Example: nested address
const data = [
{ "name": "Alice", "address": { "city": "NYC", "zip": "10001" } },
{ "name": "Bob", "address": { "city": "LA", "zip": "90001" } }
];
const flat = data.map(item => flattenObject(item));
console.log(jsonToCsv(flat));
// name,address.city,address.zip
// "Alice","NYC","10001"
// "Bob","LA","90001"
Handling Arrays in JSON
// When a field contains an array, join values or create separate rows
function flattenWithArrays(obj, prefix = '') {
const result = {};
for (const key of Object.keys(obj)) {
const newKey = prefix ? prefix + '.' + key : key;
const value = obj[key];
if (Array.isArray(value)) {
result[newKey] = value.join('; ');
} else if (value && typeof value === 'object') {
Object.assign(result, flattenWithArrays(value, newKey));
} else {
result[newKey] = value;
}
}
return result;
}
// Example: user with multiple tags
const data = [
{ "name": "Alice", "tags": ["python", "go", "rust"] },
{ "name": "Bob", "tags": ["javascript"] }
];
const flat = data.map(item => flattenWithArrays(item));
console.log(jsonToCsv(flat));
// name,tags
// "Alice","python; go; rust"
// "Bob","javascript"
Download CSV in the Browser
// Create a downloadable CSV file from JSON data
function downloadCsv(data, filename = 'export.csv') {
const csv = jsonToCsv(data);
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
// Usage
fetch('/api/users')
.then(res => res.json())
.then(data => downloadCsv(data, 'users.csv'));
Method 2: Python —Using pandas
import pandas as pd
import json
# Load JSON data
with open('data.json', 'r') as f:
data = json.load(f)
# Convert to DataFrame
df = pd.json_normalize(data) # Automatically flattens nested objects
# Export to CSV
df.to_csv('output.csv', index=False)
# Handle arrays by joining with a separator
# df.to_csv('output.csv', index=False, sep=',', quotechar='"', escapechar='\\')
# Or with more control:
df.to_csv('output.csv', index=False, encoding='utf-8',
columns=['name', 'email', 'role'], # Select columns
na_rep='N/A') # Replace NaN values
Method 3: Python —Without Dependencies
import json
import csv
def json_to_csv(data, output_path):
if not data:
return
# Flatten nested objects
def flatten(obj, prefix=''):
items = {}
for k, v in obj.items():
key = f'{prefix}.{k}' if prefix else k
if isinstance(v, dict):
items.update(flatten(v, key))
elif isinstance(v, list):
items[key] = '; '.join(str(i) for i in v)
else:
items[key] = v
return items
flat_data = [flatten(item) for item in data]
headers = list(flat_data[0].keys())
with open(output_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=headers)
writer.writeheader()
writer.writerows(flat_data)
# Usage
with open('data.json') as f:
data = json.load(f)
json_to_csv(data, 'output.csv')
Edge Cases and Gotchas
CSV fields containing commas, quotes, or newlines must be wrapped in double quotes. Double quotes within values are escaped by doubling them (""). Most CSV libraries handle this automatically, but manual implementations often miss edge cases with Unicode characters, null bytes, or Windows line endings.
Not every JSON object will have the same keys. When generating CSV headers, collect all unique keys from all objects first. Missing values should be empty strings or a placeholder like N/A —not undefined or null, which break CSV parsers.
JSON dates are often ISO 8601 strings (2026-04-29T08:00:00Z). When converting to CSV, consider formatting them for the target audience —Excel-friendly dates, locale-specific formats, or Unix timestamps depending on the use case.
For small datasets (under 10,000 rows), any method works fine. For larger datasets, pandas with chunksize parameter or streaming CSV writers will prevent memory issues. Always test with a sample of your actual data before processing the full dataset.
Quick Comparison: JavaScript vs Python
| Feature | JavaScript | Python (pandas) |
|---|---|---|
| Setup | Zero dependencies | pip install pandas |
| Nested objects | Manual flatten function | json_normalize() |
| Large files | Streaming with generators | chunksize parameter |
| Type handling | Manual coercion | Automatic dtype inference |
| Browser export | Blob + download | N/A (server-side only) |
| Best for | Frontend apps, Node.js scripts | Data analysis, ETL pipelines |
Key Takeaways
- Always flatten nested JSON objects before converting to CSV —flat key-value pairs are the CSV contract
- Handle arrays by joining values with a delimiter or exploding into separate rows
- Use pandas
json_normalize()for Python —it handles flattening automatically - Escape special characters (commas, quotes, newlines) properly in CSV fields
- Test with your actual data —synthetic tests miss real-world edge cases like missing keys and null values
- For browser-based conversion, create a Blob and trigger download via a temporary anchor element