Hey everyone! 👋
If you work in IT and constantly deal with APIs or data analytics, you've probably had to convert a complex JSON file into a CSV.
Most JSON-to-CSV converters choke on nested data. You drop in an API response with embedded objects and arrays, and you get either a broken CSV or [object Object] in every cell. 🤦♂️
I got tired of this and built JSONCSV.tools — a zero-backend converter that flattens deeply nested JSON into clean, spreadsheet-ready CSV. Here's how it works under the hood.
🛑 The Problem
Say you have this API response:
[
{
"id": 1,
"name": "Jane",
"address": {
"city": "Kyiv",
"zip": "01001"
},
"tags": ["admin", "verified"]
}
]
Enter fullscreen mode Exit fullscreen mode
Most converters will give you:
| id | name | address | tags |
|---|---|---|---|
| 1 | Jane | [object Object] |
admin,verified |
That's useless. What you actually want for a spreadsheet is this:
| id | name | address.city | address.zip | tags.0 | tags.1 |
|---|---|---|---|---|---|
| 1 | Jane | Kyiv | 01001 | admin | verified |
🧩 The Flatten Algorithm
The core of the tool is a recursive flattenObject() function with dot-notation key concatenation. Here is how I built it:
const MAX_DEPTH = 10; // Prevents infinite recursion on circular refs
function flattenObject(obj, prefix = '', separator = '.', depth = 0) {
if (depth > MAX_DEPTH) {
return { [prefix || '(too deep)']: '[MAX_DEPTH exceeded]' };
}
const result = {};
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
const newKey = prefix ? `${prefix}${separator}${key}` : key;
const value = obj[key];
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
// Recurse into nested objects
Object.assign(result, flattenObject(value, newKey, separator, depth + 1));
} else if (Array.isArray(value)) {
// Arrays get numeric indices: tags.0, tags.1
value.forEach((item, index) => {
if (item !== null && typeof item === 'object') {
Object.assign(result, flattenObject(item, `${newKey}.${index}`, separator, depth + 1));
} else {
result[`${newKey}.${index}`] = item;
}
});
} else {
result[newKey] = value;
}
}
return result;
}
Enter fullscreen mode Exit fullscreen mode
Key design decisions:
-
MAX_DEPTH = 10— prevents stack overflow (crashing the app) on circular references or absurdly deep nesting. -
hasOwnPropertyguard — avoids flattening inherited prototype properties. -
Array indexing (
tags.0,tags.1) — preserves order and structure, which is much safer than just joining array items with commas.
Unflatten: The Reverse
Converting CSV back to nested JSON is trickier. The unflatten algorithm:
- Splits each dot-notation key (
user.address.city) into path segments. - Detects numeric segments (
tags.0) and creates arrays instead of objects. - Reconstructs the nested structure with proper type coercion (e.g., turning
"true"intotrue,"42"into42, but leaving"01001"as a string to preserve leading zeros).
⚡ Architecture: Zero Backend & Web Workers
The entire app runs 100% client-side. No server. No uploads. Your data never leaves your browser. 🛡️
User drops file → Main thread reads it → Web Worker flattens/unflattens → Blob download
Why a Web Worker?
JavaScript is single-threaded. Flattening a 10MB JSON file on the main thread will freeze the UI for seconds. The Web Worker runs the heavy computation off-thread, keeping the UI responsive. (I also use the excellent Papa Parse library to handle CSV serialization inside the worker).
📦 The npm Library
I know some of you just need the logic for your own Node.js backends or React apps, so I extracted the flatten/unflatten engine into a standalone, zero-dependency npm package!
npm install json-csv-flatten
Enter fullscreen mode Exit fullscreen mode
const { flatten, unflatten } = require('json-csv-flatten');
const flat = flatten({
user: { name: 'Jane', address: { city: 'Kyiv' } }
});
// → { 'user.name': 'Jane', 'user.address.city': 'Kyiv' }
const nested = unflatten(flat);
// → { user: { name: 'Jane', address: { city: 'Kyiv' } } }
Enter fullscreen mode Exit fullscreen mode
⭐ GitHub: github.com/Amletus/json-csv-flatten
🚀 The Public Roadmap
The web tool currently handles files up to 1MB / 500 rows for free — which is more than enough for quick, one-off conversions and day-to-day tasks. (This limit also ensures the browser doesn't run out of memory for average users).
I wanted to launch fast and validate the core engine first. But I have some huge features planned for the web app, including:
- Unlimited file processing for massive API dumps
- Batch conversion (multiple files at once)
- Custom separators (dot, underscore, slash, arrow)
- TSV + Excel (.xlsx) export
- Column reorder (drag & drop columns before downloading)
If you need any of these features, let me know in the comments! I will build whatever the community asks for first.
What's next? Since the core engine is working so well, I plan to add a few more handy developer utilities (like Base64 and JWT decoders) to the site over time. A small toolkit that respects your privacy.
(Note: The current UI was scaffolded by AI so I could focus 100% on building the core flattening engine. If the tool gets enough traction, I will sit down and give it a proper redesign!)
🎉 Try It Out
Check out the tool and let me know what you think:
👉 Online tool: jsoncsv.tools
📦 npm: npm install json-csv-flatten
⭐ GitHub: github.com/Amletus/json-csv-flatten
It’s built with vanilla JS. No heavy frameworks. No dependencies (except Papa Parse). Feedback is absolutely welcome! 💻🔥
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.