Skip to content

TS: parse with an explicit record type and columns: function may error with "Type 'keyof T' is not assignable to type 'ColumnOption<string>'" #480

@beta382

Description

@beta382

Describe the bug

csv-parse v6.2.1, sync API, typescript

When supplying an explicit record type to parse<T>, and when using the columns option with function type, the accepted type of columns does not properly resolve to (record: T) => (keyof T)[]. It instead resolves to (record: string[]) => string[] (there are other types in the union, which I've excluded for brevity).

It appears that the intention is for it to resolve to (record: T) => (keyof T)[], given the logic here and here. However, the type arguments are not passed through here. This means that Options["columns"] will always default to T = string[], and thus the ternary linked to yields string.

This can cause TS compile failures if keyof T cannot be trivially coerced to string (e.g. if they're not string, or if they're coming from some other generic type).

To Reproduce

Here is a minimal TS example. The expectation is that it should compile.

test("should compile", () => {
  interface Foobar {
    1: string;
    2: string;
  }

  const columnMapping = new Map<string, keyof Foobar>([
    ["col_1", 1],
    ["col_2", 2],
  ]);

  const records = parse<Foobar>('col_1,col_2\n"foo","bar"', {
    columns: (header) =>
      header.map((column) => columnMapping.get(column)),
  });

  expect(records).toEqual([{ 1: "foo", 2: "bar" }]);
});

Instead it errors with:

Type '(header: string[]) => (keyof Foobar | undefined)[]' is not assignable to type 'true | ColumnOption<string>[] | ((record: string[]) => ColumnOption<string>[])'.
  Type '(header: string[]) => (keyof Foobar | undefined)[]' is not assignable to type '(record: string[]) => ColumnOption<string>[]'.
    Type '(keyof Foobar | undefined)[]' is not assignable to type 'ColumnOption<string>[]'.
      Type 'keyof Foobar | undefined' is not assignable to type 'ColumnOption<string>'.
        Type '1' is not assignable to type 'ColumnOption<string>'.

Workaround

(differs slightly based on the specific scenario)

const records = parse<Foobar>('col_1,col_2\n"foo","bar"', {
  columns: (header) =>
    header.map((column) => columnMapping.get(column) as undefined),
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions