How to Read Excel Cell Values as Exact Strings in Node.js Using ExcelJS
Understanding the ExcelJS Type Conversion Problem
When working with Excel files in Node.js using exceljs, developers often encounter issues where cell values are automatically converted into JavaScript data types. For example, decimal numbers like 243,11 stored in localized formats (such as Hungarian or German) or numeric floating-point values can end up transformed into imprecise floats like 243.10999999999999.
This happens because Excel stores numbers internally as IEEE 754 double-precision floating-point numbers unless the cell is explicitly typed as text. When exceljs parses the file, it extracts the underlying raw numeric value rather than the formatted display string, leading to precision artifacts in JavaScript.
How ExcelJS Represents Cell Values
In exceljs, a cell object contains several properties:
cell.value: The raw underlying value, which can be a number, string, Date object, or an object representing formulas or rich text.cell.text: The formatted display string calculated based on the cell's format rules (numFmt).cell.type: An enum indicating the data type (e.g.,ValueType.Number,ValueType.String,ValueType.Formula).
Solution: Creating a Robust Value Extractor
Relying solely on cell.text or direct string casting can fail if the cell contains complex objects like formulas, rich text, or missing format masks. To reliably extract the string value as written, you should handle each exceljs cell type explicitly.
Here is a complete, production-ready solution that extracts exact string values from any cell:
const exceljs = require('exceljs');
function getCellStringValue(cell) {
if (cell.value === null || cell.value === undefined) {
return '';
}
// 1. If formatted display text exists and is valid, prefer cell.text
if (cell.text !== undefined && cell.text !== null && cell.text !== '') {
return String(cell.text);
}
const val = cell.value;
// 2. Handle Rich Text objects
if (typeof val === 'object' && Array.isArray(val.richText)) {
return val.richText.map(part => part.text).join('');
}
// 3. Handle Formula objects
if (typeof val === 'object' && val.result !== undefined) {
return String(val.result);
}
// 4. Handle Hyperlinks
if (typeof val === 'object' && val.hyperlink) {
return String(val.text || val.hyperlink);
}
// 5. Fix IEEE 754 floating point imprecision for standard numbers
if (typeof val === 'number') {
// Fixes artifacts like 243.10999999999999 back to 243.11
return String(Number(val.toPrecision(12)));
}
return String(val);
}
async function readExcelFile(excelFile) {
const table = [];
const workbook = new exceljs.Workbook();
await workbook.xlsx.readFile(excelFile);
const worksheet = workbook.getWorksheet(1);
worksheet.eachRow({ includeEmpty: false }, (row) => {
const rowValues = [];
row.eachCell({ includeEmpty: true }, (cell) => {
rowValues.push(getCellStringValue(cell));
});
table.push(rowValues);
});
return table;
}Handling Localized Decimal Separators (Commas vs. Dots)
If your Excel files use localized formatting (such as a comma , for decimal points) and you need to preserve that exact character when converting numbers back to strings, you can format the float output using String.prototype.replace() or toLocaleString():
function formatLocalizedNumber(numValue, locale = 'hu-HU') {
if (typeof numValue === 'number') {
const cleanNum = Number(numValue.toPrecision(12));
return cleanNum.toLocaleString(locale);
}
return String(numValue);
}Key Takeaways
- Always check
cell.textfirst when reading Excel files in ExcelJS to capture the formatted display value. - Use
toPrecision(12)to strip away floating-point inaccuracies introduced by binary floating-point representation. - Inspect
cell.valuefor complex types like rich text, hyperlink objects, and formula results to ensure no data is missed or rendered as[object Object].