“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' }, …] |
| Keeps | styles, formulas, merges, cell types | values only |
| Use for | save/restore, versioning, re-rendering | APIs, 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
- Need this as a ready-made tool? Try the online XLSX → JSON converter, or the live demo with source.
- Pulling data into an API or database? See Extract Excel data as JSON for your app.
- Want an LLM to clean or classify the data? See Batch-process spreadsheet data with AI.
- Full reference: ReoGrid JSON Format · XLSX Import & Export.