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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions