October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk7 min

How to Flatten Deeply Nested JSON into CSV in Next.js with a Web Worker

A practical Next.js App Router pattern for converting nested JSON to CSV in the browser, with explicit array and null rules, CSV escaping, and a dedicated Web Worker.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To convert deeply nested JSON into CSV in a Next.js App Router app, define how objects and arrays map to columns, then run parsing, flattening, and CSV serialization in a dedicated Web Worker. Keep file selection, progress, errors, and downloads in a small Client Component. A worker keeps that computation off the page’s main thread, but it does not guarantee a speedup or prove that the whole application never sends data over the network.

Choose the CSV mapping before writing code

CSV represents rows and fields; it does not define how nested JSON structures should be laid out. The converter below uses a deliberately explicit policy:

As an Amazon Associate I earn from qualifying purchases.

  • Each top-level array item becomes one CSV record. A single top-level object becomes one record.
  • Nested objects become columns named with dot-separated paths, such as customer.name.
  • Arrays within a record are kept in one cell as JSON text. They do not create additional rows or indexed columns.
  • Explicit null becomes the text null. Missing properties produce an empty cell.
  • Empty objects and arrays do not create columns. If there are no scalar values at all, the converter reports that it has no columns to export.
  • Flattened paths are escaped so literal dots and backslashes in object keys cannot collide with path separators. Duplicate headers are also rejected rather than silently overwritten.

For example, this input:

{
  "customer": { "name": "Ava", "active": true },
  "order": { "id": 17 },
  "note": "first, then "quoted""
}

produces one record with headers customer.name, customer.active, order.id, and note. Its last cell is quoted and the embedded quotation marks are doubled. For an array of records, the converter takes the union of headers across all records and leaves a field empty wherever a record lacks that property.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the browser UI in a Client Component

Next.js App Router pages and layouts are Server Components by default. Browser APIs and interactive behavior belong in a Client Component marked with use client; keep that boundary narrow and render static page content on the server where practical. See Next.js Server and Client Components and the use client directive.

The page component can render a client-side converter. The file input, worker lifecycle, status messages, and download link all live in that client component:

// app/json-csv/page.tsx
import JsonCsvConverter from './JsonCsvConverter';

export default function Page() {
  return (
    <main>
      <h1>JSON to CSV</h1>
      <p>Select a JSON file to convert it in your browser.</p>
      <JsonCsvConverter />
    </main>
  );
}

Put parsing, flattening, and serialization in a worker

A Web Worker runs in a separate context and exchanges messages with its owning page; it cannot update the page DOM directly. The main thread should own UI state while the worker returns a result or error. See MDN’s Web Workers guide.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

This example uses a module worker created from a bundler-recognized URL. Confirm this worker entry-point pattern with the bundler and Next.js version used by your project, since the cited framework guidance does not establish one packaging setup for every toolchain.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Define and serialize the flattened records

Save the worker logic in app/json-csv/csv.worker.ts. It gathers headers in first-seen order, uses own properties to distinguish a missing field from a present value, and serializes each cell—including headers—using CSV escaping rules.

type Cell = string;
type FlatRow = Record<string, Cell>;

function escapeSegment(segment: string): string {
  return segment.replace(/\/g, '\\').replace(/./g, '\.');
}

function csvField(value: string): string {
  if (/[",rn]/.test(value)) {
    return `"${value.replace(/"/g, '""')}"`;
  }
  return value;
}

function cellText(value: unknown): string {
  if (value === null) return 'null';
  if (Array.isArray(value)) return JSON.stringify(value);
  if (typeof value === 'object') return '';
  return String(value);
}

function flatten(value: unknown, prefix = '', row: FlatRow = {}): FlatRow {
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
    if (prefix) row[prefix] = cellText(value);
    return row;
  }

  for (const [key, child] of Object.entries(value)) {
    const path = prefix ? `${prefix}.${escapeSegment(key)}` : escapeSegment(key);
    if (child !== null && typeof child === 'object' && !Array.isArray(child)) {
      flatten(child, path, row);
    } else {
      row[path] = cellText(child);
    }
  }
  return row;
}

self.onmessage = (event: MessageEvent<{ text: string }>) => {
  try {
    const parsed: unknown = JSON.parse(event.data.text);
    const inputs = Array.isArray(parsed) ? parsed : [parsed];
    if (!inputs.every(item => item !== null && typeof item === 'object' && !Array.isArray(item))) {
      throw new Error('The JSON root must be an object or an array of objects.');
    }

    const rows = inputs.map(item => flatten(item));
    const headers = [...new Set(rows.flatMap(row => Object.keys(row)))];
    if (headers.length === 0) throw new Error('No scalar fields were found to export.');

    const csv = [
      headers.map(csvField).join(','),
      ...rows.map(row =>
        headers.map(header => csvField(Object.hasOwn(row, header) ? row[header] : '')).join(',')
      )
    ].join('rn');

    self.postMessage({ ok: true, csv });
  } catch (error) {
    self.postMessage({
      ok: false,
      error: error instanceof Error ? error.message : 'Conversion failed.'
    });
  }
};

The example treats a root array as a list of records, not as an array-valued field. Nested arrays inside a record stay intact as JSON text; that choice avoids multiplying rows or inventing column names, but consumers that require one scalar per cell may prefer a different policy. Because values with no path are skipped, a top-level primitive is rejected by the root-shape check rather than exported as a row.

Connect the worker to file selection and download

In app/json-csv/JsonCsvConverter.tsx, read the selected file as text and send it to the worker. The file’s text is passed rather than a parsed object, so parsing also happens off the main thread. The browser receives the resulting CSV and creates a downloadable Blob URL; revoke the previous URL when a new conversion starts and when the component unmounts.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
'use client';

import { useEffect, useRef, useState } from 'react';

type WorkerReply = { ok: true; csv: string } | { ok: false; error: string };

export default function JsonCsvConverter() {
  const workerRef = useRef<Worker | null>(null);
  const urlRef = useRef<string | null>(null);
  const [status, setStatus] = useState('Choose a JSON file.');
  const [downloadUrl, setDownloadUrl] = useState('');

  useEffect(() => {
    const worker = new Worker(new URL('./csv.worker.ts', import.meta.url), { type: 'module' });
    workerRef.current = worker;
    worker.onmessage = (event: MessageEvent<WorkerReply>) => {
      if (!event.data.ok) {
        setStatus(event.data.error);
        return;
      }
      const url = URL.createObjectURL(new Blob([event.data.csv], { type: 'text/csv;charset=utf-8' }));
      urlRef.current = url;
      setDownloadUrl(url);
      setStatus('Conversion complete.');
    };
    worker.onerror = () => setStatus('The conversion worker failed. Try again with a valid JSON file.');

    return () => {
      worker.terminate();
      if (urlRef.current) URL.revokeObjectURL(urlRef.current);
    };
  }, []);

  async function handleFile(event: React.ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (!file || !workerRef.current) return;
    if (urlRef.current) URL.revokeObjectURL(urlRef.current);
    urlRef.current = null;
    setDownloadUrl('');
    setStatus('Reading and converting…');
    try {
      workerRef.current.postMessage({ text: await file.text() });
    } catch {
      setStatus('Could not read that file. Select it again or choose another JSON file.');
    }
  }

  return (
    <section>
      <label>
        JSON file
        <input type="file" accept="application/json,.json" onChange={handleFile} />
      </label>
      <p role="status">{status}</p>
      {downloadUrl && (
        <a href={downloadUrl} download="converted.csv">Download CSV</a>
      )}
    </section>
  );
}

In a production component, account for multiple selections while a conversion is already running—for example, attach a request ID to each message and ignore replies that do not match the latest request. Add a progress protocol only if the worker can report meaningful stages; a single synchronous conversion cannot send intermediate progress while it is busy without being structured to yield or divide work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Why a worker may not make every conversion faster

Worker messages commonly use structured cloning to copy data between contexts. Sending a large parsed object to a worker and then copying a large result back can consume time and memory of its own. Sending the source text once, parsing and converting there, and returning the final CSV avoids transferring the parsed object in both directions, but still involves moving the text and output. Measure your actual inputs and target devices; there is no source-backed universal size threshold or speedup figure.

A worker running locally does not establish that the entire application is private or offline. That claim depends on the surrounding code and network behavior as well as the worker. Inspect the application’s data path before promising that user files never leave the browser.

CSV escaping and compatibility

RFC 4180 describes common CSV conventions. It says: “Fields containing line breaks (CRLF), double quotes, and commas should be enclosed in double-quotes.” Embedded double quotes are doubled. The serializer applies this rule to both headers and values, and uses CRLF between records; every data row follows the same header order and has one cell per header. See RFC 4180.

The RFC is informational, and CSV consumers differ. If a downstream tool requires a particular encoding, delimiter, line-ending convention, or spreadsheet-specific behavior, make that compatibility target explicit and test a representative export with that tool.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not use Next.js’s worker script strategy for this job

Next.js documents <Script strategy="worker"> as experimental, requiring the nextScriptWorkers flag and available only in the Pages Router, not the App Router. That feature uses Partytown to offload third-party scripts; it is not a general-purpose worker setup for parsing and converting application data. See the Next.js Scripts guide. Framework documentation can change, so check the guide for the version you deploy.

Validate the edge cases that define your output

  • Test commas, quotation marks, CRLF and LF newlines in keys and values; verify quotes are doubled and fields remain in the intended columns.
  • Test records with different nested keys; confirm missing properties become empty cells and explicit nulls become null.
  • Test keys that contain dots or backslashes, plus empty objects and arrays, to ensure headers follow the documented naming and omission rules.
  • Test empty input arrays, malformed JSON, and arrays containing non-object items; surface a clear error instead of downloading a misleading file.
  • Try representative files on the devices and browsers your application supports. Track memory and responsiveness as well as elapsed time, because the worker and cloned messages have costs too.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.