-
Notifications
You must be signed in to change notification settings - Fork 675
fix(csv): function parse option fieldsPerRecord when negative records may have less fields #7153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -228,16 +228,26 @@ export function convertRowToObject( | |
| row: readonly string[], | ||
| headers: readonly string[], | ||
| zeroBasedLine: number, | ||
| /* allow less fields in a row than headers */ | ||
| allowLessRowFields = false, | ||
| ) { | ||
| if (row.length !== headers.length) { | ||
| if (!allowLessRowFields && row.length !== headers.length) { | ||
| throw new Error( | ||
| `Syntax error on line ${ | ||
| zeroBasedLine + 1 | ||
| }: The record has ${row.length} fields, but the header has ${headers.length} fields`, | ||
| ); | ||
| } | ||
| if (allowLessRowFields && row.length > headers.length) { | ||
| throw new Error( | ||
| `Syntax error on line ${ | ||
| zeroBasedLine + 1 | ||
| }: The record has ${row.length} fields, but the header allows maximum ${headers.length} fields`, | ||
| ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two issues here:
|
||
| } | ||
| const out: Record<string, unknown> = {}; | ||
| for (const [index, header] of headers.entries()) { | ||
| if (index === row.length) break; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: a mid-loop const limit = Math.min(row.length, headers.length);
for (let i = 0; i < limit; i++) {
out[headers[i]] = row[i];
}or |
||
| out[header] = row[index]; | ||
| } | ||
| return out; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -524,7 +524,12 @@ export function parse<const T extends ParseOptions>( | |
|
|
||
| const zeroBasedFirstLineIndex = options.skipFirstRow ? 1 : 0; | ||
| return r.map((row, i) => { | ||
| return convertRowToObject(row, headers, zeroBasedFirstLineIndex + i); | ||
| return convertRowToObject( | ||
| row, | ||
| headers, | ||
| zeroBasedFirstLineIndex + i, | ||
| (options?.fieldsPerRecord ?? 0) < 0, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you adopt the suggestion to push the |
||
| ); | ||
| }) as ParseResult<ParseOptions, T>; | ||
| } | ||
| return r as ParseResult<ParseOptions, T>; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -309,6 +309,29 @@ Deno.test({ | |
| ); | ||
| }, | ||
| }); | ||
|
|
||
| await t.step({ | ||
| name: "Allow less row fields than columns", | ||
| fn() { | ||
| const input = "a,b,c\nd,e"; | ||
| const output = [{ | ||
| foo: "a", | ||
| bar: "b", | ||
| baz: "c", | ||
| }, { | ||
| foo: "d", | ||
| bar: "e", | ||
| }]; | ||
| assertEquals( | ||
| parse(input, { | ||
| skipFirstRow: false, | ||
| columns: ["foo", "bar", "baz"], | ||
| fieldsPerRecord: -1, | ||
| }), | ||
| output, | ||
| ); | ||
| }, | ||
| }); | ||
| await t.step({ | ||
| name: "NegativeFieldsPerRecord", | ||
| fn() { | ||
|
|
@@ -320,6 +343,19 @@ Deno.test({ | |
| assertEquals(parse(input, { fieldsPerRecord: -1 }), output); | ||
| }, | ||
| }); | ||
| await t.step({ | ||
| name: "LessFieldsPerRecordThanFirstLine", | ||
| fn() { | ||
| const input = `a,b,c\nd,e`; | ||
| const output = [ | ||
| { a: "d", "b": "e" }, | ||
| ]; | ||
| assertEquals( | ||
| parse(input, { skipFirstRow: true, fieldsPerRecord: -1 }), | ||
| output, | ||
| ); | ||
| }, | ||
| }); | ||
| await t.step({ | ||
| name: "FieldCount", | ||
| fn() { | ||
|
|
@@ -860,6 +896,22 @@ c"d,e`; | |
| ); | ||
| }, | ||
| }); | ||
| await t.step({ | ||
| name: "mismatching number of headers and fields 3", | ||
| fn() { | ||
| const input = "a,b,c\nd,e,,g"; | ||
| assertThrows( | ||
| () => | ||
| parse(input, { | ||
| skipFirstRow: true, | ||
| columns: ["foo", "bar", "baz"], | ||
| fieldsPerRecord: -1, | ||
| }), | ||
| Error, | ||
| "Syntax error on line 2: The record has 4 fields, but the header allows maximum 3 fields", | ||
| ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test name |
||
| }, | ||
| }); | ||
| await t.step({ | ||
| name: "Strips leading byte-order mark with bare cell", | ||
| fn() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two small nits:
//for one-line comments — that's the convention in this file (see e.g.parse.ts:229).allowLessRowFields→allowFewerRowFields("fewer" for countables).Also, threading a boolean flag through an internal helper for one call-site is a bit awkward. Consider passing
fieldsPerRecord(or{ variable: boolean }) so the policy lives inside the helper rather than at every caller — this would also make it easier to fixCsvParseStreamsymmetrically.