2

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"
            }
        },
    ]
}
Craig C
  • 71
  • 4
  • 1
    I strongly believe that this https://stackoverflow.com/questions/64744734/typescript-keyof-index-type-is-too-wide answer will help you. I would even say, that your question is dublicate – captain-yossarian from Ukraine Nov 20 '20 at 19:42
  • This does appear to be related. I'll look over that question and see if I can apply it to my situation. – Craig C Nov 20 '20 at 20:08
  • 1
    You're right, that was exactly what I needed. Thank you for pointing me in the right direction. – Craig C Nov 20 '20 at 20:18

1 Answers1

2

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"
            }
        },
    ]
}
Craig C
  • 71
  • 4