ReoGrid ReoGrid Web

Convert XLSX to JSON in the browser — no server, no upload

· unvell team
Convert XLSX to JSON in the browser — no server, no upload

“Convert XLSX to JSON” usually means shipping the file to a backend, parsing it with a library, and sending data back. That round-trip is slow, and it puts confidential spreadsheets on someone else’s server.

There’s a better way: convert the file in the browser. ReoGrid reads .xlsx on the client, so the bytes never leave the user’s device — and because it also renders the file in an editable grid, you can show the spreadsheet and hand back JSON from the same component.

This post covers both flavors of “JSON” you might actually want, and when to use each.


Two kinds of JSON

Before writing code, decide what “JSON” means for your use case:

ReoGrid JSON (lossless)Data rows (plain)
Shape{ format, version, workbook, styles, … }[{ Product: 'Widget', Price: '9.99' }, …]
Keepsstyles, formulas, merges, cell typesvalues only
Use forsave/restore, versioning, re-renderingAPIs, databases, AI, analytics

If you want to reopen the file later exactly as it was, use ReoGrid JSON. If you want the data to feed something else, use data rows. We’ll do both.


Setup

xlsx import works in both the free Lite tier and Pro, but Lite truncates files past 100 rows × 26 columns — so for converting real-world files, use Pro.

import { createReogrid } from '@reogrid/pro';

const grid = createReogrid('#grid', { licenseKey: 'YOUR-LICENSE-KEY' });
<input type="file" accept=".xlsx" id="file" />
<div id="grid" style="width: 100%; height: 420px;"></div>

Flavor 1 — lossless ReoGrid JSON

Load the file, then call toJson(). That’s the whole conversion:

const input = document.querySelector<HTMLInputElement>('#file')!;

input.addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (!file) return;

  await grid.loadFromFile(file);   // parse + render the .xlsx
  const doc = grid.toJson();       // whole workbook → ReoGrid JSON

  console.log(JSON.stringify(doc, null, 2));
});

grid.toJson() serializes every sheet plus the active-sheet index. The result is a ReoGrid JSON document — lossless, so it round-trips everything xlsx does and the ReoGrid-only features (custom cell types, all conditional-format rules). Restore it any time with grid.loadJson(doc).

To convert just the active sheet rather than the whole workbook:

import { stringifyReoGridJson } from '@reogrid/pro';

await grid.loadFromFile(file);
const json = stringifyReoGridJson(grid.worksheet, { pretty: true });

Download the result

function downloadJson(doc: unknown, filename: string) {
  const blob = new Blob([JSON.stringify(doc, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = Object.assign(document.createElement('a'), { href: url, download: filename });
  a.click();
  URL.revokeObjectURL(url);
}

await grid.loadFromFile(file);
downloadJson(grid.toJson(), file.name.replace(/\.xlsx$/i, '.json'));

Flavor 2 — plain data rows

For most app integrations you don’t want styles and formula sources — you want the values, as an array of objects keyed by the header row. Read them out of the grid after loading:

import type { ReogridInstance } from '@reogrid/pro';

function sheetToRows(grid: ReogridInstance): Record<string, string>[] {
  const ws = grid.worksheet;

  // Find the used extent from the sparse cell snapshot.
  const snapshot = ws.getExportSnapshot();
  let lastRow = 0, lastCol = 0;
  for (const cell of snapshot.cells) {
    lastRow = Math.max(lastRow, cell.row);
    lastCol = Math.max(lastCol, cell.column);
  }

  // Header row → keys. getDisplayText returns the *rendered* value.
  const headers: string[] = [];
  for (let c = 0; c <= lastCol; c++) {
    headers[c] = (ws.getDisplayText(0, c) || `col${c}`).trim();
  }

  // Data rows → objects.
  const rows: Record<string, string>[] = [];
  for (let r = 1; r <= lastRow; r++) {
    const row: Record<string, string> = {};
    for (let c = 0; c <= lastCol; c++) row[headers[c]] = ws.getDisplayText(r, c) ?? '';
    rows.push(row);
  }
  return rows;
}
await grid.loadFromFile(file);
const rows = sheetToRows(grid);
// [{ Product: 'Widget', Price: '9.99', Qty: '40' }, …]

The key detail is getDisplayText, not cell.value: getDisplayText returns the rendered value with formulas evaluated and number formats applied ("$9.99", "1,200"). cell.value returns the raw input — which for a formula cell is the source string ("=B2*1.1"), not the result.


Why client-side wins

  • Privacy. The file is read with the browser File API and parsed on the canvas. Nothing is uploaded — safe for financial, HR, or otherwise sensitive spreadsheets. (This is exactly how our free online XLSX viewer works.)
  • No backend. No upload endpoint, no parsing service, no temp-file cleanup. Static hosting is enough.
  • Instant + visible. The user sees the spreadsheet render as it converts, so they can confirm they picked the right file.

Where to go next

Try ReoGrid Web in your project

Canvas-based Excel-compatible spreadsheet component for React and Vue. Lite is free — start with one npm install.

Related articles

Stay Updated

Be first to know — get updates as they ship

Get notified of new releases, features, and announcements.
No spam — just updates that matter.