typescriptenums

Enforce object to contain all keys of an enum and still have type inference for it's values


I have an object and I want to enforce it to contain all keys of an Enum, and I also want the type of it’s values to be inferred. So if I do this:

enum RequiredKeys {
    A = 'a',
    B = 'b'
}
const objectThatShouldContainAllRequiredKeys = {
    [RequiredKeys.A]: (id: string) => {}
};
// Argument of type '123' is not assignable to parameter of type 'string'
// Which is great, that's exactly what I want. 
objectThatShouldContainAllRequiredKeys[RequiredKeys.A](123);

But now, I tried enforcing the object keys and every solution I try breaks the type inference. For example:

enum RequiredKeys {
    A = 'a',
    B = 'b'
}
// Property 'b' is missing in type '{ a: (id: string) => void; }' but required in type 'Record<RequiredKeys, Function>'.
// Which is great, that's exactly what I want
const objectThatShouldContainAllRequiredKeys: Record<RequiredKeys, Function> = {
    [RequiredKeys.A]: (id: string) => {}
};
// No error here, which is less great...
objectThatShouldContainAllRequiredKeys[RequiredKeys.A](123);

Any idea how I can enjoy both worlds? Have the object enforce all keys from the enum and infer the object values? Thanks!!


Solution

  • You can create identity function with type parameter constrained to have required keys, so typescript will validate the passed object keys and will infer its values' types:

    const createWithRequiredKeys = <T extends Record<RequiredKeys, unknown>>(obj: T) => obj;
    
    const withRequiredKeys = createWithRequiredKeys({
        [RequiredKeys.A]: (id: string) => {},
        [RequiredKeys.B]: 'foo',
    }); 
    
    // withRequiredKeys is { a: (id: string) => void; b: string; }
    
    withRequiredKeys[RequiredKeys.A](123); // 'number' is not assignable to parameter of type 'string'
    

    Playground