spiffytech
spiffytech

Reputation: 6632

Return type annotation for calling an object's methods

How can I write a return type annotation for a function that takes an object, calls all of its methods, and returns a new object with the original keys mapped to the methods' return values?

function callMethods<T>(obj: T) {                                                                                                                                   
    const objResults = {};                                                                                                                                           
    Object.keys(obj).forEach((prop) => objResults[prop] = obj[prop]({}));                                                                                            

    return objResults;                                                                                                                                               
}                                                                                                                                                                 

type MethodArgs = any // some complex object                                                                                                                      

console.log(callMethods({                                                                                                                                           
    a: (_args: MethodArgs): number => 1,                                                                                                                          
    b: (_args: MethodArgs): string => "one",                                                                                                                      
    c: (_args: MethodArgs): number[] => [1]                                                                                                                       
}));                                                                                                                                                              
// => {a: 1, b: "one", c: [1]}
// This object's type should be {a: number, b: string, c: number[]}

Upvotes: 2

Views: 1711

Answers (2)

aleksxor
aleksxor

Reputation: 8370

As of [email protected] has been added mapped types and in [email protected] conditional types and type inferrence. This one got really easy:

type MappedReturnTypes<
    T extends Record<string, (...args: any[]) => any>
> = { [K in keyof T]: ReturnType<T[K]> }

playground link

Upvotes: 0

toskv
toskv

Reputation: 31612

Right now there is no way of properly retrieving the return type of a method call and as such my solution is only partial. However there is a proposal in the works and you can read more about it here.

The best you could is at least get some more typing out of the thing you have now.

One thing you can do is use mapped types in order to retrieve the keys from T and use them as keys in the return value.

function callMethods<T>(obj: T) {
    return Object.keys(obj).reduce((previous, current) => {
        previous[current] = obj[current]({});
        return previous;
    }, {} as {[P in keyof T]: any});
}

Because the return type of the methods can't be determined the value type of properties of the returned object is going to be any.

If the return types are finite you could define them as a type and use them (it's not perfect but it might be better).

type ReturnTypes = number | string | number[];

function callMethods<T>(obj: T) {
    return Object.keys(obj).reduce((previous, current) => {
        previous[current] = obj[current]({});
        return previous;
    }, {} as {[P in keyof T]: ReturnTypes});
}

If both the return types and the type of the object being passed in is known you could pass those as external parameters too, so you make a more generic function.

type ReturnTypes = number | string | number[];
interface Methods {
    a: (args: any) => number,
    b: (args: any) => string,
    c: (args: any) => number[],

}

function callMethods<T, V>(obj: T) {
    return Object.keys(obj).reduce((previous, current) => {
        previous[current] = obj[current]({});
        return previous;
    }, {} as {[P in keyof T]: V});
}


let result = callMethods<Methods, ReturnTypes>({
    a: (_args): number => 1,
    b: (_args): string => "one",
    c: (_args): number[] => [1]
});

While this isn't the perfect solution I hope it helps you.

Note: please forgive the rewrite of the method, it seemed cleaner to use reduce.

Upvotes: 3

Related Questions