The two JSON shapes, and which one you want
Given a sheet like this:
A B C
1 name role salary
2 Ada engineer 90000
3 Grace admiral 95000An array of objects treats row 1 as keys. This is what almost every API, database import and JavaScript codebase expects:
[
{ "name": "Ada", "role": "engineer", "salary": 90000 },
{ "name": "Grace", "role": "admiral", "salary": 95000 }
]An array of arrays keeps the raw grid, header row included. Use it when column order matters, when the sheet has no header row, or when duplicate column names would collide as object keys:
[
["name", "role", "salary"],
["Ada", "engineer", 90000],
["Grace", "admiral", 95000]
]Excel dates are numbers, and that is the main trap
A spreadsheet does not store 2026-03-14. It stores 46095 — a count of days since 1 January 1900 — plus a display format telling Excel to render it as a date. A naive conversion reads the number and writes 46095 into your JSON, which then looks like plausible data and is not.
This tool reads the cell’s format alongside its value and writes dates in a readable form instead. Two related quirks worth knowing if you ever do this conversion elsewhere: the epoch is off by one because Excel deliberately reproduces a Lotus 1-2-3 bug that treats 1900 as a leap year, and workbooks saved by older Mac versions of Excel count from 1904 instead, so the same number means a date four years apart depending on the file.
What else does not survive the way you expect
- Formulas become their results. A workbook caches the last computed value of every formula, and that cached value is what you get. If the file was written by a tool that never calculated, those cells can be empty.
- Merged cells hold one value. Only the top-left cell of a merged range contains anything; the others are genuinely empty in the file. Merged title rows are the usual reason a converted sheet has a header of
nullvalues. - Leading zeros are already gone. If a postcode or product code was stored as a number, Excel dropped the zeros when the file was saved. No conversion can recover them — the information is not in the file.
- Displayed values are not stored values. A cell showing
3.14may hold3.14159265. Rounding is a display format; the JSON gets the full number. - Nothing about appearance carries over. Colours, fonts, conditional formatting, column widths, comments and charts have no JSON representation and are dropped.
Header rows that are not header rows
Real workbooks often start with a title, a blank row and a logo before the actual table begins. Converting with the object shape then produces keys like Sales report Q3 and __EMPTY_1. Delete everything above the real header row before converting — the conversion trusts the first row absolutely, and cleaning up afterwards in JSON is far more work than deleting three rows in Excel.
The same applies to trailing content. A totals row at the bottom becomes a data record indistinguishable from the rest, and a footnote in column A becomes a row with one populated field.
Several sheets
Each sheet becomes its own JSON document rather than being merged, because sheets in one workbook rarely share a schema — merging them would produce records with mostly-empty fields. Convert the sheets you need one at a time and combine them afterwards if you actually want one array.
.xls as well as .xlsx
Both are read. .xlsx is the modern zipped XML format; .xls is the pre-2007 binary one, still produced by plenty of legacy exports and capped at 65,536 rows. If an .xls export looks truncated, that limit is the first thing to check — the data may have been lost before you received the file.