Rob Murphy
Rob Murphy

Reputation: 953

How can I get Typescript to understand what the value type of a homogenous object is without losing keys from static declaration?

How can I get Typescript to understand what the value type of an object is without losing access to the key information from its static declaration?

In the below code I want the benefit of

This object is homogenous and will have lots of functions, specifying the signature every time seems like a pain.

Example 1 - TS doesn't know the keys of the object

    interface Foo {
        [key: string]: (n: number) => any
    }
    
    const obj1: Foo = {
        // TS already knows that `n` is a number 🙂
        bar(n) {return n}
    }
    
    type KeyOfObj1 = keyof typeof obj1
    const kob1: KeyOfObj1 = "xyz" // This does not fail static type checking 😢
    

Example 2 - TS doesn't know the signature of the functions which are the object's values

    const obj2 = {
        // Notice here I have to specify the type of `n` 😢
        bar(n: number) {return n}
    }
    
    type KeyOfObj2 = keyof typeof obj2
    const kob2: KeyOfObj2 = "xyz" // This fails static type checking 🙂

How can I get the best of both worlds?

p.s. I understand I could pull out the keys into an enum but that seems rather ugly and repetitive, though perhaps it really is the only way 😕

Upvotes: 3

Views: 163

Answers (1)

kaya3
kaya3

Reputation: 51132

This is a fairly common situation: you want to write an object, have its members contextually typed according to some weaker type (Foo in this case), but still retain the stronger type for some other purpose (in this case, you don't want to "forget" what the keys are).

The usual thing to do is to write a generic identity function, like below, which checks that the argument type T is a subtype of Foo, while returning the stricter type T:

function foo<T extends Foo>(obj: T): T {
    return obj;
}

Then when you create the object, the compiler will contextually type it as if it is some subtype of Foo, but still keep that subtype (with its specific key names) for the assignment to obj:

const obj = foo({
    foo(n) { return n + 1; },
    bar(n) { return n * 2; },
});

// 'foo' | 'bar'
type ObjKeys = keyof typeof obj

Playground Link

Unfortunately, at least right now, you do need the generic identity function for this to work. There is currently a proposal to add new syntax to the language which would allow this kind of problem to be solved without adding a "useless" extra function.

Upvotes: 4

Related Questions