Search code examples
typescripttypescript-typingstypescript2.0tsc

Mapping TypeScript types from Object => Object


Say I have an object like so:

export const v = {
   a() : Promise<X>{

   },
   b() : Promise<Y>{

   },
   c() : Promise<Z>{

   }
}

my question is - is there any way to get the type of v, but map the type, so that it looks something like this:

export type V = {
  a: X, 
  b: Y,
  c: Z
}

basically I am mapping each key in the object, to the resolved value of the respective promise.

Basically, I am trying to derive a modified type, from something that's declared statically.


Solution

  • You can do this using conditional types and mapped type:

    class X{}
    class Y{}
    class Z{}
    export const v = {
        a() : Promise<X>{
            return null as any;
        },
        b() : Promise<Y>{
            return null as any;
        },
        c() : Promise<Z>{
            return null as any;
        },
    }
    
    type ExtractAllPromisses<T> = 
    { 
        // Take all keys of T ([P in keyof T])
        // and if the property P of T is a promise returning function (T[P] extends ()=> Promise<infer U>)
        // then the new type of P will be the return type of the promise (saved in U)
        // Otherwise the new type of P is never
        [P in keyof T]: T[P] extends ()=> Promise<infer U> ? U : never 
    };
    
    export type V = ExtractAllPromisses<typeof v> // same as type V = { a: X; b: Y; c: Z; }
    

    There are variations you can do in the conditional type depending on your need, the example above works specifically for a type which has just functions that take no arguments and return a Promise

    If you want to match a function with any number of arguments you can use:

    type ExtractAllPromisses<T> = { [P in keyof T]: T[P] extends (...args: any[])=> Promise<infer U> ? U : never };
    

    If your type also has properties that are not promise returning functions and you want to preserve those properties (ie v also has a field foo:number) you can use:

    type ExtractAllPromisses<T> = { [P in keyof T]: T[P] extends (...args: any[])=> Promise<infer U> ? U : T[P] };
    

    If you want to exclude properties that are not a promise retuning function. You can filter the keys:

    type PromiseFunctionFields<T> = { [P in keyof T] : T[P] extends (...args: any[])=> Promise<any> ? P : never}[keyof T];
    type ExtractAllPromisses<T> = { [P in PromiseFunctionFields<T>]: T[P] extends (...args: any[])=> Promise<infer U> ? U : T[P] };