Using dynamic keys on object with typescript

I have a function like this

interface Cat {
    color: string,
    weight: number,
    cute: Boolean, // eventhough all cats are cute!
}

export const doSomething = (
    cat: Array<Cat| null>,
    index: number,
    key:  keyof typeof cat,
    payload: string | number | Boolean
) => {
    ....
    cat[key] = payload
    ....
}

This gives me

Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type

Which I understand is because TypeScript thinks that key can be any string instead one of "color", "weight", "cute".
How would I tell in the function declaration that key is one of the three ("color", "weight", "cute")?

I tried

...
key:  keyof Cat,
...

Without luck. This

 cat[key] = payload

Gives me now

Type ‘string| number | Boolean | ‘ is not assignable to type ‘(string & number & Boolean )

>Solution :

cat is an array so keyof typeof cat are the keys of the array not the Cat interface. For those you can use keyof Cat

export const doSomething = (
    cat: Array<Cat | null>,
    index: number,
    key: keyof Cat,
    payload: string | number | Boolean
) => {
    cat.forEach(c => {
        if (c) {
            c[key] = payload
        }
    });
}

This still doesn’t quite work, because there is no correlation between key and payload you could call doSomething(.., ..., 'cute', 1).

You need to add a generic type parameter to tie the payload to the key:

export const doSomething = <K extends keyof Cat>(
    cat: Array<Cat | null>,
    index: number,
    key: K,
    payload: Cat[K]
) => {
    let c = cat[index];
    if (c) {
        c[key] = payload
    }
}

Playground Link

The K in the code above is called a generic type parameter. A function can have generic type parameters in order to capture extra information from the call site making it a generic function. K extends keyof Cat means that K must be a subtype of keyof Cat so it could be one of "color", "weight", "cute".

So when we call doSomething([], 0, "cute", true), K will be "cute". Since we now have the information about which key the function was called with, we can use it in a type query (Cat[K]) to get the actual type of the property with key "cute".

Leave a Reply