Narrowing type of T[K] when using K extends keyof T

Viewed 526

I'm trying to define an interface where:

  • one property is the key of a generic type
  • another property relies on the type of the value associated with that key from the other property

The closest I can get is for Typescript to resolve T[K] to the union types of all values of T. But it seems like there should be some way to narrow that further if K is a known string literal.

Here's an example of what I'm trying to do.

Test

interface Person {
    age: number;
    name: string;
}

interface ColumnDef<T, K extends keyof T> {
    key: K;
    renderData: (value: T[K]) => void;
}

interface Report<T> {
    columns: ColumnDef<T, keyof T>[];
}

const report: Report<Person> = {
    columns: [
        {
            key: "age", // this is correctly typed to be "age"|"name"
            renderData: (value) => {
                // ideally value should be "number" here, but it is "string|number"
            }
        },
        {
            key: "name", // this is correctly typed to be "age"|"name"
            renderData: (value) => {
                // ideally value should be "string" here, but it is "string|number"
            }
        },
    ]
}
1 Answers

Following the related question linked from captain-yossarian, I was able to narrow the type using this answer.

In case it helps someone further, here is how I applied that to my example case.

interface Person {
    age: number;
    name: string;
}

type ColumnDef<T> = {
    [K in keyof T]-?: BaseColumnDef<T, K>
  }[keyof T]


interface BaseColumnDef<T, K extends keyof T> {
    key: K;
    renderData: (value: T[K]) => void;
}

interface Report<T> {
    columns: ColumnDef<T>[];
}

const report: Report<Person> = {
    columns: [
        {
            key: "age", // this is correctly typed to be "age"|"name"
            renderData: (value) => {
                // value is now type "number"
            }
        },
        {
            key: "name", // this is correctly typed to be "age"|"name"
            renderData: (value) => {
                // value is now type "string"
            }
        },
    ]
}
Related