Home > Enterprise >  Inferring a generic type based on an index signature in TypeScript
Inferring a generic type based on an index signature in TypeScript

Time:12-04

I have a little TypeScript library I use for data analysis, which involves me specifying the names of a CSV file's column headings and then reusing them quite a bit throughout my code.

I've been trying to improve my code so that these column names are inferred, so my IDE will start to offer them as autocompletion suggestions, but I'm having trouble getting that inference to work in the way I had expected.

Here's a minimal reproducible example of my problem:

interface GenericInterface<T extends string> {
    cols: {
        [key in T]: string | number;
    },
}

// I expect T to be inferred as 'NAME' | 'COUNTRY'
const fileInfoA: GenericInterface = {
    cols: {
        NAME: 'A',
        COUNTRY: 'B',
    },
};

TypeScript Playground

As the comment mentions, in this example I would have expected the generic type T to have been inferred as 'NAME' | 'COUNTRY' based on the property names of the object in my cols property. However, instead TypeScript is telling me that "Generic type 'GenericInterface' requires 1 type argument(s)".

Of course, if I do provide that string union type explicitly, the error goes away. But in some cases I am dealing with CSV files where I need to know the names of 200 columns, and I really don't want to have to repeat myself by including both that union and the object with each of those properties.

I feel like I'm either missing something very obvious or trying to do something that TypeScript doesn't support. But nothing I've been able to find in the TypeScript documentation or by searching for answers elsewhere online has brought me to a solution.

CodePudding user response:

You were very close. In order to infer T you need to create extra function.

interface GenericInterface<T extends string> {
    cols: {
        [key in T]: string | number;
    },
}


const foo = <T extends string>(arg: GenericInterface<T>) => arg

// <"NAME" | "COUNTRY">(arg: GenericInterface<"NAME" | "COUNTRY">)
foo({
    cols: {
        NAME: 'A',
        COUNTRY: 'B',
    },
})

Now, T is infered as "NAME" | "COUNTRY".

You can find more information about inference on function arguments in my article

Here you can find documentation

  • Related