The other day I was having difficulties while using an Excel library. The usecase was to read an Excel row and convert the fetched cells into their string representation to present to the user. The code looked something like:
const excelRow: Library.Cell[] = library.fetchRow(rowIndex);
const cleanedRow: string[] = excelRow.map(cellToString);
function cellToString(cell: Library.Cell): string;
The problem was that, in the FE, when I was accessing the array element via index to be display it,
I was seeing undefined being displayed. How could that be if I always convert to a string?
I first made sure that this wasn’t a trick in the data, maybe someone wrote undefined in
a cell. This was not the case, the cell was definitely empty. So I did what anyone debugging
JS would do: I used JSON.stringify in the excelRow and in cleanedRow (before and after).
I saw something like this:
excelRow: [
{ type: "string", value: "Data" },
null,
{ type: "string", value: "DE" },
]
cleanedRow: [
"Data",
null,
"DE"
]
I’m not making any type assertions in the cellToString method so if the input type is correct, the
Typescript compiler should guarantee that everything is converted to string.
I checked the Cell type, it has null as a possible value and I was handling it directly
in my logic. In desperation, I made the method disregard whatever value in the cell was present
and always return a dummy value:
function cellToString(cell: Library.Cell): string {
return "DummyValue";
}
It didn’t work. I was still seeing undefined when displaying things. Why was this element not being converted?

At this point I was a bit lost. I then noticed a few things that led me to the answer:
- I was getting
nullin my debug messages, but findingundefinedwhen it was being displayed. - When I added a debug message to the conversion function (
cellToString), I was not seeing anynullas input ever. - The amount of calls to the conversion function (
cellToString) was not nearly as many as the number of cells in the row.
This made me sure of one thing: the cellToString function is working, the issue is that the empty
cells are being skipped. And for some reason these skipped cells show as null in JSON.stringify
but as undefined if accessing via an index.
Enter Sparse Arrays
As it turns out, arrays in JavaScript come in two variants: dense and sparse. The Excel library I was As it turns out, arrays in JavaScript come in two variants: dense 1 and sparse. The Excel library I was using was returning sparse arrays when reading a row. But what are they? Code makes it easy to understand:
const denseArray = [1, undefined, 3];
const sparseArray = [1, , 3];
denseArray.length === sparseArray.length; // true
JSON.stringify(denseArray) === JSON.stringify(sparseArray); // true
denseArray[1] === sparseArray[1]; // true
console.log(sparseArray); // [1, empty, 3]
console.log(denseArray); // [1, undefined, 3]
const mappedDense = denseArray.map((_e) => 4); // [4, 4, 4]
const mappedSparse = sparseArray.map((_e) => 4); // [4, empty, 4]
Sparse arrays have actual holes in them for when elements are missing. This is not the same as the element
itself being undefined or null, this is a special case where the element itself is not present. The
quirk here is that iteration methods like .map or .forEach completely skip these elements. Accessing
them via index gives us undefined, but this one actually means that the element is missing, not that the
value of the element is undefined.
The Fix
The fix is simple: convert to a proper dense array. The most straightforward way is with Array.from().
const excelRow: Library.Cell[] = Array.from(library.fetchRow(rowIndex));
As it turns out, I would have easily understood the issue if I didn’t have a debug alias that always
wraps my variables with JSON.stringify, console.log clearly shows empty when the index is missing.
As a final note, beware of using the Array constructor to build a X element array without it being
initialised, this creates sparse arrays so might not behave as you expect it to. Deleting an element using delete method does the same thing.
-
‘Dense array’ isn’t an official ECMAScript term, but it’s commonly used. ↩︎