From 2054da435f5bad1ec90186d6c152d22261b20020 Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Wed, 9 Jul 2025 23:11:45 -0700 Subject: [PATCH] Fix bug where `defaultValue` was not set if row is uneven --- src/normalization.js | 8 ++++---- test/normalization.test.js | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/normalization.js b/src/normalization.js index 45a864a..bc43d0b 100644 --- a/src/normalization.js +++ b/src/normalization.js @@ -73,7 +73,7 @@ export function expandScientificNotation(str, truncate = false) { * @property {'string'|'number'|'bigint'|'date'} [type='string'] The JavaScript data type to attempt to cast this column to. If * the data cannot be unmangled, the data passes through as a `string`. The default value is `'string'`. * @property {string} [displayName=name] Optional `string` value to indicate the desired header name in the output CSV. - * @property {string} [defaultValue] Optional `string` value to use to fill empty CSV fields. If no value is provided, the + * @property {string} [defaultValue=''] Optional `string` value to use to fill empty CSV fields. If no value is provided, the * field value will be `''` (empty string) in the output CSV. */ @@ -122,7 +122,7 @@ export class CSVNormalizer extends TransformStream { this.#passthroughEmptyRows = options.passthroughEmptyRows ?? false; this.#typeCastOnly = options.typeCastOnly ?? false; - for (const { name, type, displayName = name, defaultValue = null } of headers) { + for (const { name, type, displayName = name, defaultValue = '' } of headers) { let normalizedType = type?.toLowerCase() ?? 'string'; if (!['string', 'number', 'bigint', 'date'].includes(normalizedType)) { console.warn(`Type "${normalizedType}" is not supported, defaulting to string.`); @@ -162,7 +162,7 @@ export class CSVNormalizer extends TransformStream { /** @type {Array} */ const out = []; for (const { name, type, displayName, defaultValue, index } of this.#columns) { - let value = row[index]; + let value = row[index] ?? ''; const emptyField = value === '' ? true : false; switch (type) { case 'string': break; @@ -170,7 +170,7 @@ export class CSVNormalizer extends TransformStream { case 'bigint': value = this.#typeCastOnly ? BigInt(value).toString() : CSVNormalizer.fixExcelBigInt(value); break; case 'date': value = this.#typeCastOnly ? new Date(value).toISOString() : CSVNormalizer.fixExcelDate(value); break; } - if (emptyField && defaultValue !== null) { + if (emptyField) { value = defaultValue; } out.push({ name, displayName, value, emptyField }); diff --git a/test/normalization.test.js b/test/normalization.test.js index a2e2466..fd7ef7a 100644 --- a/test/normalization.test.js +++ b/test/normalization.test.js @@ -293,6 +293,29 @@ suite('CSVNormalizer', { concurrency: true }, () => { ['', '', 's3'] ])); }); + test('handles uneven CSV', { concurrency: true }, async () => { + await createCSVMockStream([ + ['common', 'maybe1', 'maybe2', 'maybe3'], + ['c'], + ['c', 'm'], + ['c', 'm', 'm'], + ['c', 'm', 'm', 'm'] + ]) + .pipeThrough(new CSVNormalizer([ + { name: 'common', type: 'string' }, + { name: 'maybe1', type: 'string' }, + { name: 'maybe2', type: 'string' }, + { name: 'maybe3', type: 'string' }, + ])) + .pipeThrough(new CSVDenormalizer()) + .pipeTo(csvStreamEqualWritable([ + ['common', 'maybe1', 'maybe2', 'maybe3'], + ['c', '', '', ''], + ['c', 'm', '', ''], + ['c', 'm', 'm', ''], + ['c', 'm', 'm', 'm'], + ])); + }); test('defaults to "string" type', { concurrency: true }, async () => { await createCSVMockStream([ ['columnA', 'columnB'], -- 2.47.3