typescriptunionmapped-types

Typescript: How do I create a mapped type from an union of object with single keys?


What I want to do is the following:

Say I have a type:

type UpdateUnion =
    {foo:number} |
    {bar:string} |
    {baz: {value1: number, value2: string}};

Is there a way to construct a mapped type like this?

type UpdateFunctions = {
    foo: (value: number) => void,
    bar: (value: string) => void,
    baz: (value: {value1: number, value2:string}) => void
};  

So far I've tried this:

type KeysOfUnion<T> = T extends T ? keyof T : never;
type UpdateFunctions = {
    [Key in KeysOfUnion<UpdateUnion>]: (value: UpdateUnion[Key]) => void
}

This only seems to work if there is only one possible value in the union, otherwise I get the error: Type 'Key' cannot be used to index type 'UpdateUnion'


Solution

  • As an alternative approach, you can use key remapping to get the desired key:

    type UpdateFunctions<T> = {
      [K in T as keyof K]: (value: K[keyof K]) => void;
    };
    

    Key remapping allows us to change the key to something else. For instance, in the case, if we want to create a setter function for some property. If we have a property foo: number we would need to create setFoo: (value: number) => void. Example without key remapping:

    type Obj = {
      foo: string;
      bar: number;
      oth: boolean;
    };
    
    type CreateSetters<T> = {
      [K in keyof T]: (value: T[K]) => void;
    };
    
    // type Result = {
    //     foo: (value: string) => void;
    //     bar: (value: number) => void;
    //     oth: (value: boolean) => void;
    // }
    type Result = CreateSetters<Obj>;
    

    With key remapping:

    type CreateSetters<T> = {
      [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;
    };
    
    // type Result = {
    //     setFoo: (value: string) => void;
    //     setBar: (value: number) => void;
    //     setOth: (value: boolean) => void;
    // }
    type Result = CreateSetters<A>;
    

    In the latter case, instead of K we use set${Capitalize<K & string>, which creates a key that starts with set and capitalized K which is achieved by using the built-in utility type Capitalize. Note that K is string | number | symbol and Capitalize accepts only string, thus we take the intersection of K with string, which guarantees that it will be a string.

    Usage:

    // type Result = {
    //   foo: (value: number) => void;
    //   bar: (value: string) => void;
    //   baz: (value: {
    //       value1: number;
    //       value2: string;
    //   }) => void;
    // }
    type Result = UpdateFunctions<UpdateUnion>
    

    TS Playground