From a3a86ecaef69a76c1ebf9934a7a59a595a2f986b Mon Sep 17 00:00:00 2001 From: lux-liang <249971141+lux-liang@users.noreply.github.com> Date: Fri, 9 Oct 2026 11:19:43 +0800 Subject: [PATCH 1/2] fix(csv-parse): honor normalized objname in result collection --- packages/csv-parse/lib/index.js | 6 +- packages/csv-parse/lib/sync.js | 3 +- packages/csv-parse/test/option.objname.js | 104 ++++++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) diff --git a/packages/csv-parse/lib/index.js b/packages/csv-parse/lib/index.js index 4aefe2a26..ab5538d62 100644 --- a/packages/csv-parse/lib/index.js +++ b/packages/csv-parse/lib/index.js @@ -101,13 +101,11 @@ const parse = function () { const parser = new Parser(options); if (callback) { const records = - options === undefined || options.objname === undefined - ? [] - : Object.create(null); + parser.options.objname === undefined ? [] : Object.create(null); parser.on("readable", function () { let record; while ((record = this.read()) !== null) { - if (options === undefined || options.objname === undefined) { + if (parser.options.objname === undefined) { records.push(record); } else { Object.assign(records, { diff --git a/packages/csv-parse/lib/sync.js b/packages/csv-parse/lib/sync.js index d0298920f..030642544 100644 --- a/packages/csv-parse/lib/sync.js +++ b/packages/csv-parse/lib/sync.js @@ -5,8 +5,9 @@ const parse = function (data, opts = {}) { if (typeof data === "string") { data = Buffer.from(data); } - const records = opts && opts.objname ? Object.create(null) : []; const parser = transform(opts); + const records = + parser.options.objname === undefined ? [] : Object.create(null); const push = (record) => { if (parser.options.objname === undefined) records.push(record); else { diff --git a/packages/csv-parse/test/option.objname.js b/packages/csv-parse/test/option.objname.js index 5329dbdda..e408dcd0d 100644 --- a/packages/csv-parse/test/option.objname.js +++ b/packages/csv-parse/test/option.objname.js @@ -1,5 +1,7 @@ import "should"; +import assert from "node:assert/strict"; import { parse } from "../lib/index.js"; +import { parse as parseSync } from "../lib/sync.js"; describe("Option `objname`", function () { describe("validation", function () { @@ -11,4 +13,106 @@ describe("Option `objname`", function () { ); }); }); + + describe("normalized result mode", function () { + for (const objname of [undefined, null, false]) { + it(`returns complete array records with objname ${objname}`, function (next) { + const input = "alice,A,extra\nbob,B,extra"; + const expected = [ + ["alice", "A", "extra"], + ["bob", "B", "extra"], + ]; + assert.deepEqual(parseSync(input, { objname }), expected); + parse(input, { objname }, (err, records) => { + if (err) return next(err); + assert.deepEqual(records, expected); + next(); + }); + }); + + it(`returns column records with objname ${objname}`, function (next) { + const input = "name,value\nalice,A\nbob,B"; + const options = { objname, columns: true }; + const expected = [ + { name: "alice", value: "A" }, + { name: "bob", value: "B" }, + ]; + assert.deepEqual(parseSync(input, options), expected); + parse(input, options, (err, records) => { + if (err) return next(err); + assert.deepEqual(records, expected); + next(); + }); + }); + + it(`returns an empty array with objname ${objname}`, function (next) { + assert.deepEqual(parseSync("", { objname }), []); + parse("", { objname }, (err, records) => { + if (err) return next(err); + assert.deepEqual(records, []); + next(); + }); + }); + } + + for (const objname of [0, 1]) { + it(`returns a serializable dictionary for column index ${objname}`, function (next) { + const input = "alice,A\nbob,B"; + const expected = { + [objname === 0 ? "alice" : "A"]: ["alice", "A"], + [objname === 0 ? "bob" : "B"]: ["bob", "B"], + }; + const syncRecords = parseSync(input, { objname }); + assert.equal(Object.getPrototypeOf(syncRecords), null); + assert.equal(JSON.stringify(syncRecords), JSON.stringify(expected)); + parse(input, { objname }, (err, records) => { + if (err) return next(err); + assert.deepEqual(records, syncRecords); + next(); + }); + }); + } + + it("preserves reserved property names in the first column", function () { + const keys = ["length", "__proto__", "constructor", "toString", "2", ""]; + const input = keys.map((key) => `${key},value`).join("\n"); + const records = parseSync(input, { objname: 0 }); + assert.equal(Object.getPrototypeOf(records), null); + assert.deepEqual( + JSON.parse(JSON.stringify(records)), + Object.fromEntries(keys.map((key) => [key, [key, "value"]])), + ); + }); + + it("preserves raw records and info with the first column as key", function (next) { + const input = "alice,A\nbob,B"; + const options = { objname: 0, raw: true, info: true }; + const syncRecords = parseSync(input, options); + assert.equal(Object.getPrototypeOf(syncRecords), null); + parse(input, options, (err, records) => { + if (err) return next(err); + assert.deepEqual(records, syncRecords); + assert.deepEqual(records.alice.record, ["alice", "A"]); + assert.equal(records.alice.raw, "alice,A\n"); + assert.equal(records.alice.info.records, 1); + next(); + }); + }); + + for (const objname of [0, 1, "key"]) { + it(`returns an empty dictionary with objname ${objname}`, function (next) { + const options = { + objname, + ...(typeof objname === "string" ? { columns: true } : {}), + }; + const expected = Object.create(null); + assert.deepEqual(parseSync("", options), expected); + parse("", options, (err, records) => { + if (err) return next(err); + assert.deepEqual(records, expected); + next(); + }); + }); + } + }); }); From dad8ff73210f946b886bde80081b2e0e0b482b4c Mon Sep 17 00:00:00 2001 From: lux-liang <249971141+lux-liang@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:43:51 +0800 Subject: [PATCH 2/2] test(csv-parse): keep minimal objname regressions --- packages/csv-parse/test/option.objname.js | 104 +++------------------- 1 file changed, 14 insertions(+), 90 deletions(-) diff --git a/packages/csv-parse/test/option.objname.js b/packages/csv-parse/test/option.objname.js index e408dcd0d..261873a3c 100644 --- a/packages/csv-parse/test/option.objname.js +++ b/packages/csv-parse/test/option.objname.js @@ -15,101 +15,25 @@ describe("Option `objname`", function () { }); describe("normalized result mode", function () { - for (const objname of [undefined, null, false]) { - it(`returns complete array records with objname ${objname}`, function (next) { - const input = "alice,A,extra\nbob,B,extra"; - const expected = [ - ["alice", "A", "extra"], - ["bob", "B", "extra"], - ]; - assert.deepEqual(parseSync(input, { objname }), expected); - parse(input, { objname }, (err, records) => { - if (err) return next(err); - assert.deepEqual(records, expected); - next(); - }); - }); - - it(`returns column records with objname ${objname}`, function (next) { - const input = "name,value\nalice,A\nbob,B"; - const options = { objname, columns: true }; - const expected = [ - { name: "alice", value: "A" }, - { name: "bob", value: "B" }, - ]; - assert.deepEqual(parseSync(input, options), expected); - parse(input, options, (err, records) => { - if (err) return next(err); - assert.deepEqual(records, expected); - next(); - }); - }); - - it(`returns an empty array with objname ${objname}`, function (next) { - assert.deepEqual(parseSync("", { objname }), []); - parse("", { objname }, (err, records) => { - if (err) return next(err); - assert.deepEqual(records, []); - next(); - }); - }); - } - - for (const objname of [0, 1]) { - it(`returns a serializable dictionary for column index ${objname}`, function (next) { - const input = "alice,A\nbob,B"; - const expected = { - [objname === 0 ? "alice" : "A"]: ["alice", "A"], - [objname === 0 ? "bob" : "B"]: ["bob", "B"], - }; - const syncRecords = parseSync(input, { objname }); - assert.equal(Object.getPrototypeOf(syncRecords), null); - assert.equal(JSON.stringify(syncRecords), JSON.stringify(expected)); - parse(input, { objname }, (err, records) => { - if (err) return next(err); - assert.deepEqual(records, syncRecords); - next(); - }); - }); - } - - it("preserves reserved property names in the first column", function () { - const keys = ["length", "__proto__", "constructor", "toString", "2", ""]; - const input = keys.map((key) => `${key},value`).join("\n"); - const records = parseSync(input, { objname: 0 }); - assert.equal(Object.getPrototypeOf(records), null); + it("returns a dictionary with the first column as key", function () { + const records = parseSync("alice,A\nbob,B", { objname: 0 }); assert.deepEqual( - JSON.parse(JSON.stringify(records)), - Object.fromEntries(keys.map((key) => [key, [key, "value"]])), + records, + Object.assign(Object.create(null), { + alice: ["alice", "A"], + bob: ["bob", "B"], + }), ); }); - it("preserves raw records and info with the first column as key", function (next) { - const input = "alice,A\nbob,B"; - const options = { objname: 0, raw: true, info: true }; - const syncRecords = parseSync(input, options); - assert.equal(Object.getPrototypeOf(syncRecords), null); - parse(input, options, (err, records) => { - if (err) return next(err); - assert.deepEqual(records, syncRecords); - assert.deepEqual(records.alice.record, ["alice", "A"]); - assert.equal(records.alice.raw, "alice,A\n"); - assert.equal(records.alice.info.records, 1); - next(); - }); - }); - - for (const objname of [0, 1, "key"]) { - it(`returns an empty dictionary with objname ${objname}`, function (next) { - const options = { - objname, - ...(typeof objname === "string" ? { columns: true } : {}), - }; - const expected = Object.create(null); - assert.deepEqual(parseSync("", options), expected); - parse("", options, (err, records) => { + for (const objname of [null, false]) { + it(`returns complete array records with objname ${objname}`, function (next) { + parse("alice,A,extra\nbob,B,extra", { objname }, (err, records) => { if (err) return next(err); - assert.deepEqual(records, expected); + assert.deepEqual(records, [ + ["alice", "A", "extra"], + ["bob", "B", "extra"], + ]); next(); }); });