Oleksandr Karpenko

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.
  • hasOwnProperty guard — 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:

  1. Splits each dot-notation key (user.address.city) into path segments.
  2. Detects numeric segments (tags.0) and creates arrays instead of objects.
  3. Reconstructs the nested structure with proper type coercion (e.g., turning "true" into true, "42" into 42, 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! 💻🔥