How XLSX to JSON is built
XLSX to JSON converts an Excel sheet into a JSON array or JSONL (newline-delimited JSON). This post covers the one data type that needs real handling on the way out — dates — and a library-versioning surprise found while making legacy .xls files work too.
Tech used
SheetJS with dates resolved as real Date objects
As in XLSX to CSV and XLS to XLSX Converter, parsing runs on SheetJS Community Edition, installed from the project’s own CDN and loaded via dynamic import('xlsx') so its ~500 KB stays out of the initial bundle. The read call passes cellDates: true, which asks SheetJS to hand back JavaScript Date objects for date-formatted cells instead of Excel’s native representation — a serial number counting days since a fixed epoch. That serial-number form is meaningless outside a spreadsheet; a JSON consumer expects an actual date value, not 45292.
JSON vs. JSONL: one array vs. many small ones
Standard JSON represents “a list of rows” as a single array — [ {...}, {...}, {...} ] — which has to be parsed as one complete value. JSONL (JSON Lines / newline-delimited JSON) represents the same data as one JSON object per line, with no enclosing array at all. That difference matters once the output is meant to feed something else downstream: a JSONL file can be streamed and processed one line at a time — piped through jq, read incrementally by a script — without ever holding the whole file in memory, which is exactly the shape json-to-sqlite-style ingestion tools expect from log-like data. The tool offers both, rather than picking one and forcing a conversion step for whichever consumer needs the other.
Implementation & operational notes
A Date object can’t go into JSON.stringify as itself — it has to become a string first, deliberately. normalizeJsonValue() walks every value recursively and converts any Date to an ISO 8601 string via toISOString() before serialization, rather than relying on JSON.stringify’s own default Date handling (which does the same ISO conversion silently). Making the conversion explicit means a malformed date — one that produces Invalid Date — is caught and reported as a real error instead of silently serializing as the string "Invalid Date", which would otherwise sit in the output looking like valid data.
A library’s compiled type declarations didn’t match its actual runtime export, and only running the real import caught it. This is the same issue documented in XLS to XLSX Converter: SheetJS 0.20.3’s code-page module (needed for reading legacy .xls files with non-ASCII text) exports cptable, utils, and version at runtime, but its bundled type declaration only describes cptable. The fix is the same here — register all three named exports, verified against a real Japanese-text .xls fixture — because trusting the type declaration alone would have shipped a version of the code-page table missing pieces the actual module provides.
Empty cells become null, not an absent key or an empty string. Both other choices are lossy or ambiguous in their own way — an absent key can’t distinguish “empty” from “this column doesn’t apply to this row,” and an empty string collides with a cell that’s genuinely empty text. null is the JSON value built for exactly this case, so that’s what an empty cell becomes.
The live preview is capped, and the actual downloadable file isn’t. Rendering a full JSON dump of a very large sheet directly into the page would be a real performance cost for a preview nobody’s going to read in full; the on-screen preview is truncated at 20,000 characters, while the file offered for download always contains the complete, untruncated conversion.
Try it / source
- Tool: XLSX to JSON
- Source: github.com/GeppettoAndRomero/xlsx-to-json