Call a member of an object with arguments

type Dictionary = { [key: string]: any }

type MethodNames<T extends Dictionary> = T extends ReadonlyArray<any>
  ? Exclude<keyof [], number>
  : { [P in keyof T]: T[P] extends Function ? P : never }[keyof T]

function apply<T extends Dictionary, P extends MethodNames<T>>(
  obj: T,
  methodName: P,
  args: Parameters<T[P]>
): ReturnType<T[P]> {
  return obj[methodName](...args);
}

// Testing object types:
const obj = { add: (...args: number[]) => {} }
apply(obj, 'add', [1, 2, 3, 4, 5])

// Testing array types:
apply([1, 2, 3], 'push', [4])

// Testing the return type:
let result: number = apply(new Map<number, number>(), 'get', [1])

Playground link

The Dictionary type allows T[P] to be used.

The Parameters and ReturnType types are baked into TypeScript.

The MethodNames type extracts any keys whose value is assignable to the Function type. Array types require a special case.

Checklist

  • Method name is validated? ✅
  • Arguments are type-checked? ✅
  • Return type is correct? ✅

I think this does the trick:

function callMethodWithArgs<
  M extends keyof T,
  T extends { [m in M]: (...args: Array<any>) => any },
  F extends T[M]
>(obj: T, methodName: M, args: Parameters<F>) {
  return obj[methodName](...args) as ReturnType<F>;
}

Requires TS 3 though!

Tags:

Typescript