From: Ardis Lu Date: Thu, 25 Sep 2025 06:33:37 +0000 (-0700) Subject: Make column matching case insensitive by default X-Git-Tag: v0.1.7~12 X-Git-Url: https://git.ardis.lu/?a=commitdiff_plain;h=4fca85f0479b71efff6ea37a49444946d27b5fb9;p=csvio Make column matching case insensitive by default Before: `datetime` and `dateTime` are considered different columns. After: `datetime` and `dateTime` are considered the same column (last column prevails). Note the output column name will still use the same casing passed in `CSVNormalizerHeader`. `CSVNormalizer` already significantly modifies column names by normalizing to camelCase, so it is surprising the column matching is still case sensitive. It is likely that in the case a user wants `column A` and `columnA` to be the same column, that they'd also want `columna` to be the same as well. --- diff --git a/src/normalization.js b/src/normalization.js index ee04dc0..eeb0c82 100644 --- a/src/normalization.js +++ b/src/normalization.js @@ -151,7 +151,9 @@ export class CSVNormalizer extends TransformStream { const normalizedRow = this.#useLiteralNames ? chunk : chunk.map(f => toCamelCase(f)); let i = 0; for (const header of normalizedRow) { - const col = this.#columns.find(c => c.name === header); + const col = this.#useLiteralNames + ? this.#columns.find(c => c.name === header) + : this.#columns.find(c => c.name.toLocaleLowerCase() === header.toLocaleLowerCase()); if (col !== undefined) { // Drop columns provided in the CSV but not the headers input col.index = i; } diff --git a/test/normalization.test.js b/test/normalization.test.js index d4a1821..d4e15b4 100644 --- a/test/normalization.test.js +++ b/test/normalization.test.js @@ -248,8 +248,8 @@ suite('CSVNormalizer', { concurrency: true }, () => { }); test('removes columns that have the same normalized name (last column prevails)', { concurrency: true }, async () => { await createCSVMockStream([ - ['columnA', 'column A', 'column-A'], - ['a1', 'a2', 'a3'] + ['columnA', 'column A', 'column-A', 'columna'], + ['a1', 'a2', 'a3', 'a4'] ]) .pipeThrough(new CSVNormalizer([ { name: 'columnA' } @@ -257,23 +257,52 @@ suite('CSVNormalizer', { concurrency: true }, () => { .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ ['columnA'], - ['a3'] + ['a4'] ])); }); test('can match by literal column names', { concurrency: true }, async () => { await createCSVMockStream([ - ['columnA', 'column A', 'column-A'], - ['a1', 'a2', 'a3'] + ['columnA', 'column A', 'column-A', 'columna'], + ['a1', 'a2', 'a3', 'a4'] ]) .pipeThrough(new CSVNormalizer([ { name: 'columnA' }, { name: 'column A' }, - { name: 'column-A' } + { name: 'column-A' }, + { name: 'columna' } + ], { useLiteralNames: true })) + .pipeThrough(new CSVDenormalizer()) + .pipeTo(csvStreamEqualWritable([ + ['columnA', 'column A', 'column-A', 'columna'], + ['a1', 'a2', 'a3', 'a4'] + ])); + }); + test('is NOT case sensitive on column names', { concurrency: true }, async () => { + await createCSVMockStream([ + ['datetime', 'dateTime'], + ['a1', 'a2'] + ]) + .pipeThrough(new CSVNormalizer([ + { name: 'datetime' } + ])) + .pipeThrough(new CSVDenormalizer()) + .pipeTo(csvStreamEqualWritable([ + ['datetime'], + ['a2'] + ])); + }); + test('is case sensitive on column names when `useLiteralNames: true`', { concurrency: true }, async () => { + await createCSVMockStream([ + ['datetime', 'dateTime'], + ['a1', 'a2'] + ]) + .pipeThrough(new CSVNormalizer([ + { name: 'datetime' } ], { useLiteralNames: true })) .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ - ['columnA', 'column A', 'column-A'], - ['a1', 'a2', 'a3'] + ['datetime'], + ['a1'] ])); }); test('removes columns that have the same literal name (last column prevails)', { concurrency: true }, async () => {