jeanpaul62
jeanpaul62

Reputation: 10561

Dynamically putting functions into an object without losing type information

In my functions.ts, I define 2 functions, both take an api object, and returns a function with different arg types, arg numbers and return types.

export function f1 (api: Api) {
  return function (a: number): number[] {/* not important */}
};
export function f2 (api: Api) {
  return function (a: string, b: string): boolean {/* not important */}
};

Now given an api: Api global object, I want to define an object, that has fields f1 and f2, and the value associated to each field is the inner function inside the above 2 functions.

Namely, manually, I would do:

const api: Api = ...;
const myObject = {
  f1: f1(api),
  f2: f2(api),
}

And this works super well.

But the next step: I wish to do it dynamically, i.e. without manually typing f1 and f2.

Here's my start:

import * as functions from './functions';

const api: Api = ...;
const myObject = Object.keys(functions).reduce((accumulator, functionName) => {
  accumulator[functionName] = functions[functionName](api);
}, {} as ???);

The code works, but the typings don't. I'm not sure what to put instead of ???. {[index: keyof typeof functions]: (...args: any[]) => any} would work, but I lose a lot of information on types.

I tried looking at TS's Parameters<T>, ReturnType<T> and I'm sure there's something to be done with infer, but can't seem to get hold of it.

Upvotes: 1

Views: 56

Answers (1)

Shaun Luttin
Shaun Luttin

Reputation: 141512

The code works, but the typings don't. I'm not sure what to put instead of ???.

Here is an approach that works. It creates a new type that maps each function name to its return type.

type ReturnTypes<T> = {
    [K in keyof T]: T[K] extends (...args: any) => any
    ? ReturnType<T[K]>
    : never;
};

const myObject = Object
    .keys(functions)
    .reduce(
        (accumulator, functionName) => {
            accumulator[functionName] = functions[functionName](api);
            return accumulator;
        },
        {} as ReturnTypes<typeof functions>
    );

const result1: number[] = myObject.f1(10);
const result2: boolean = myObject.f2('foo', 'bar');

Here it is in the TypeScript playground.

Upvotes: 1

Related Questions