Call a member of an object with arguments

Multi tool use
function whatever(object, methodName, args) {
return object[methodName](...args);
}
Can the above be typed so the the following is enforced:
methodName
is a key ofobject
.
object[methodName]
is callable and its args are...args
.- The return type of
whatever(object, methodName, args)
is the return type ofobject[methodName](...args)
.
The closest thing I could find is the definition of function.apply
, but it isn't quite the same as the above.
typescript
add a comment |
function whatever(object, methodName, args) {
return object[methodName](...args);
}
Can the above be typed so the the following is enforced:
methodName
is a key ofobject
.
object[methodName]
is callable and its args are...args
.- The return type of
whatever(object, methodName, args)
is the return type ofobject[methodName](...args)
.
The closest thing I could find is the definition of function.apply
, but it isn't quite the same as the above.
typescript
add a comment |
function whatever(object, methodName, args) {
return object[methodName](...args);
}
Can the above be typed so the the following is enforced:
methodName
is a key ofobject
.
object[methodName]
is callable and its args are...args
.- The return type of
whatever(object, methodName, args)
is the return type ofobject[methodName](...args)
.
The closest thing I could find is the definition of function.apply
, but it isn't quite the same as the above.
typescript
function whatever(object, methodName, args) {
return object[methodName](...args);
}
Can the above be typed so the the following is enforced:
methodName
is a key ofobject
.
object[methodName]
is callable and its args are...args
.- The return type of
whatever(object, methodName, args)
is the return type ofobject[methodName](...args)
.
The closest thing I could find is the definition of function.apply
, but it isn't quite the same as the above.
typescript
typescript
edited Jan 10 at 15:15


user489872
1,38231335
1,38231335
asked Jan 10 at 13:01
Jaffa The CakeJaffa The Cake
5,45122334
5,45122334
add a comment |
add a comment |
8 Answers
8
active
oldest
votes
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!
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
3
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
Jan 10 at 13:48
3
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
2
One comment:<M extends keyof T, ...
makes sure thatmethodName
is actually a key ofobj
:)
– Surma
Jan 10 at 14:55
3
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
|
show 4 more comments
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? ✅
The return type comes back asany
.
– Jaffa The Cake
Jan 10 at 13:46
Forgot theReturnType
. Fixed
– aleclarson
Jan 10 at 13:47
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
add a comment |
Is the return type always the same of someObject[methodName]?
function whatever<O extends {[key: string]: (...args) => R}, R>(object: O, methodName: keyof O, ...args: any): R {
return object[methodName](...args);
}
Then you could do this.
add a comment |
This should do it. This checks for the methodName
as well as each of the args
.
(Note: not perfect, some refinement can be made; e.g., unknown
-> any
)
type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never;
function whatever<
T extends object,
TKey extends keyof T,
TArgs extends ArgumentsType<T[TKey]>
>(
object: T,
methodName: T[TKey] extends ((...args: TArgs) => unknown) ? TKey : never,
args: TArgs
): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {
const method = object[methodName];
if (typeof method !== 'function') {
throw new Error('not a function');
}
return method(...args);
}
interface Test {
foo: (a: number, b: number) => number;
bar: string;
}
const test: Test = {
foo: (a, b) => a + b,
bar: 'not a function'
};
const result = whatever(test, 'foo', [1, 2]);
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
add a comment |
How about this?
function whatever(someObject: { [key: string]: Function}, methodName: string, args: any) {
return someObject[methodName](...args);
}
whatever({ func1: (args) => (console.log(...args)) }, 'func1', [1])
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
add a comment |
The option that is the type-safest one is to use Parameters
to get the parameter types of the function (in order for arguments to be type safe), ReturnType
to return the result of a function.
You also need to also add a type parameter to capture the actual key passed in so we can get the actual type of the function (T[K]
(
function whatever<T extends Record<string, (...a: any)=> any>, K extends keyof T> (someObject: T, methodName: K, ...args: Parameters<T[K]>) : ReturnType<T[K]>{
return someObject[methodName](...args);
}
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', [1])
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', ["1"]) // err
add a comment |
The closest result I can think of so far is the following
function whatever<T extends object>(object: T, methodName: keyof T, args: any) {
const func = object[methodName]
if (typeof func === "function") {
return func(...[args])
}
}
const obj = {
aValue: 1,
aFunc: (s: string) => "received: " + s
}
whatever(obj, "aFunc", "blabla")
which correctly checks the key for being part of the object.
The type inference for return type and args is still missing though. I will update the answer if I find a better solution.
add a comment |
There you go, meets all criteria and doesn't need type assertions.
type AnyFunction = (...args: any) => any;
function whatever<
T extends Record<PropertyKey, AnyFunction>,
K extends keyof T,
A extends Parameters<T[K]>
>(object: T, methodName: K, args: A): ReturnType<T[K]> {
return object[methodName](...args);
}
The Parameters
type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any
? P
: never;
Using the PropertyKey
type instead of string
allows you to use properties of type string | number | symbol
, which is the full gamut supported by JavaScript.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54129289%2fcall-a-member-of-an-object-with-arguments%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
8 Answers
8
active
oldest
votes
8 Answers
8
active
oldest
votes
active
oldest
votes
active
oldest
votes
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!
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
3
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
Jan 10 at 13:48
3
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
2
One comment:<M extends keyof T, ...
makes sure thatmethodName
is actually a key ofobj
:)
– Surma
Jan 10 at 14:55
3
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
|
show 4 more comments
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!
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
3
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
Jan 10 at 13:48
3
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
2
One comment:<M extends keyof T, ...
makes sure thatmethodName
is actually a key ofobj
:)
– Surma
Jan 10 at 14:55
3
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
|
show 4 more comments
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!
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!
edited Jan 10 at 15:28
Surma
835
835
answered Jan 10 at 13:31
richsilvrichsilv
7,60811627
7,60811627
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
3
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
Jan 10 at 13:48
3
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
2
One comment:<M extends keyof T, ...
makes sure thatmethodName
is actually a key ofobj
:)
– Surma
Jan 10 at 14:55
3
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
|
show 4 more comments
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
3
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
Jan 10 at 13:48
3
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
2
One comment:<M extends keyof T, ...
makes sure thatmethodName
is actually a key ofobj
:)
– Surma
Jan 10 at 14:55
3
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
1
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
Jan 10 at 13:44
3
3
I should also say: Thank you! TIL I learn
Parameters<F>
, ReturnType<F>
, and the use of m in M
.– Jaffa The Cake
Jan 10 at 13:48
I should also say: Thank you! TIL I learn
Parameters<F>
, ReturnType<F>
, and the use of m in M
.– Jaffa The Cake
Jan 10 at 13:48
3
3
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
Today I learned I learn?
– Ben Kolya Mansley
Jan 10 at 14:06
2
2
One comment:
<M extends keyof T, ...
makes sure that methodName
is actually a key of obj
:)– Surma
Jan 10 at 14:55
One comment:
<M extends keyof T, ...
makes sure that methodName
is actually a key of obj
:)– Surma
Jan 10 at 14:55
3
3
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
@BenKolyaMansley haha nbd big deal
– Jaffa The Cake
Jan 10 at 15:05
|
show 4 more comments
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? ✅
The return type comes back asany
.
– Jaffa The Cake
Jan 10 at 13:46
Forgot theReturnType
. Fixed
– aleclarson
Jan 10 at 13:47
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
add a comment |
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? ✅
The return type comes back asany
.
– Jaffa The Cake
Jan 10 at 13:46
Forgot theReturnType
. Fixed
– aleclarson
Jan 10 at 13:47
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
add a comment |
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? ✅
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? ✅
edited Jan 10 at 13:47
answered Jan 10 at 13:44
aleclarsonaleclarson
10k64064
10k64064
The return type comes back asany
.
– Jaffa The Cake
Jan 10 at 13:46
Forgot theReturnType
. Fixed
– aleclarson
Jan 10 at 13:47
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
add a comment |
The return type comes back asany
.
– Jaffa The Cake
Jan 10 at 13:46
Forgot theReturnType
. Fixed
– aleclarson
Jan 10 at 13:47
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
The return type comes back as
any
.– Jaffa The Cake
Jan 10 at 13:46
The return type comes back as
any
.– Jaffa The Cake
Jan 10 at 13:46
Forgot the
ReturnType
. Fixed– aleclarson
Jan 10 at 13:47
Forgot the
ReturnType
. Fixed– aleclarson
Jan 10 at 13:47
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
Jan 10 at 13:51
add a comment |
Is the return type always the same of someObject[methodName]?
function whatever<O extends {[key: string]: (...args) => R}, R>(object: O, methodName: keyof O, ...args: any): R {
return object[methodName](...args);
}
Then you could do this.
add a comment |
Is the return type always the same of someObject[methodName]?
function whatever<O extends {[key: string]: (...args) => R}, R>(object: O, methodName: keyof O, ...args: any): R {
return object[methodName](...args);
}
Then you could do this.
add a comment |
Is the return type always the same of someObject[methodName]?
function whatever<O extends {[key: string]: (...args) => R}, R>(object: O, methodName: keyof O, ...args: any): R {
return object[methodName](...args);
}
Then you could do this.
Is the return type always the same of someObject[methodName]?
function whatever<O extends {[key: string]: (...args) => R}, R>(object: O, methodName: keyof O, ...args: any): R {
return object[methodName](...args);
}
Then you could do this.
answered Jan 10 at 13:17


Karl MerkliKarl Merkli
283
283
add a comment |
add a comment |
This should do it. This checks for the methodName
as well as each of the args
.
(Note: not perfect, some refinement can be made; e.g., unknown
-> any
)
type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never;
function whatever<
T extends object,
TKey extends keyof T,
TArgs extends ArgumentsType<T[TKey]>
>(
object: T,
methodName: T[TKey] extends ((...args: TArgs) => unknown) ? TKey : never,
args: TArgs
): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {
const method = object[methodName];
if (typeof method !== 'function') {
throw new Error('not a function');
}
return method(...args);
}
interface Test {
foo: (a: number, b: number) => number;
bar: string;
}
const test: Test = {
foo: (a, b) => a + b,
bar: 'not a function'
};
const result = whatever(test, 'foo', [1, 2]);
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
add a comment |
This should do it. This checks for the methodName
as well as each of the args
.
(Note: not perfect, some refinement can be made; e.g., unknown
-> any
)
type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never;
function whatever<
T extends object,
TKey extends keyof T,
TArgs extends ArgumentsType<T[TKey]>
>(
object: T,
methodName: T[TKey] extends ((...args: TArgs) => unknown) ? TKey : never,
args: TArgs
): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {
const method = object[methodName];
if (typeof method !== 'function') {
throw new Error('not a function');
}
return method(...args);
}
interface Test {
foo: (a: number, b: number) => number;
bar: string;
}
const test: Test = {
foo: (a, b) => a + b,
bar: 'not a function'
};
const result = whatever(test, 'foo', [1, 2]);
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
add a comment |
This should do it. This checks for the methodName
as well as each of the args
.
(Note: not perfect, some refinement can be made; e.g., unknown
-> any
)
type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never;
function whatever<
T extends object,
TKey extends keyof T,
TArgs extends ArgumentsType<T[TKey]>
>(
object: T,
methodName: T[TKey] extends ((...args: TArgs) => unknown) ? TKey : never,
args: TArgs
): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {
const method = object[methodName];
if (typeof method !== 'function') {
throw new Error('not a function');
}
return method(...args);
}
interface Test {
foo: (a: number, b: number) => number;
bar: string;
}
const test: Test = {
foo: (a, b) => a + b,
bar: 'not a function'
};
const result = whatever(test, 'foo', [1, 2]);
This should do it. This checks for the methodName
as well as each of the args
.
(Note: not perfect, some refinement can be made; e.g., unknown
-> any
)
type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never;
function whatever<
T extends object,
TKey extends keyof T,
TArgs extends ArgumentsType<T[TKey]>
>(
object: T,
methodName: T[TKey] extends ((...args: TArgs) => unknown) ? TKey : never,
args: TArgs
): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {
const method = object[methodName];
if (typeof method !== 'function') {
throw new Error('not a function');
}
return method(...args);
}
interface Test {
foo: (a: number, b: number) => number;
bar: string;
}
const test: Test = {
foo: (a, b) => a + b,
bar: 'not a function'
};
const result = whatever(test, 'foo', [1, 2]);
answered Jan 10 at 13:32
David KhourshidDavid Khourshid
1962
1962
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
add a comment |
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
Jan 10 at 13:33
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````
– Andrei Zubov
Jan 10 at 13:38
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
This has the same pitfall as the accepted answer.
– Jaffa The Cake
Jan 10 at 13:47
add a comment |
How about this?
function whatever(someObject: { [key: string]: Function}, methodName: string, args: any) {
return someObject[methodName](...args);
}
whatever({ func1: (args) => (console.log(...args)) }, 'func1', [1])
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
add a comment |
How about this?
function whatever(someObject: { [key: string]: Function}, methodName: string, args: any) {
return someObject[methodName](...args);
}
whatever({ func1: (args) => (console.log(...args)) }, 'func1', [1])
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
add a comment |
How about this?
function whatever(someObject: { [key: string]: Function}, methodName: string, args: any) {
return someObject[methodName](...args);
}
whatever({ func1: (args) => (console.log(...args)) }, 'func1', [1])
How about this?
function whatever(someObject: { [key: string]: Function}, methodName: string, args: any) {
return someObject[methodName](...args);
}
whatever({ func1: (args) => (console.log(...args)) }, 'func1', [1])
answered Jan 10 at 13:09
Dmase05Dmase05
7641017
7641017
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
add a comment |
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.
– Andrei Zubov
Jan 10 at 13:16
add a comment |
The option that is the type-safest one is to use Parameters
to get the parameter types of the function (in order for arguments to be type safe), ReturnType
to return the result of a function.
You also need to also add a type parameter to capture the actual key passed in so we can get the actual type of the function (T[K]
(
function whatever<T extends Record<string, (...a: any)=> any>, K extends keyof T> (someObject: T, methodName: K, ...args: Parameters<T[K]>) : ReturnType<T[K]>{
return someObject[methodName](...args);
}
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', [1])
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', ["1"]) // err
add a comment |
The option that is the type-safest one is to use Parameters
to get the parameter types of the function (in order for arguments to be type safe), ReturnType
to return the result of a function.
You also need to also add a type parameter to capture the actual key passed in so we can get the actual type of the function (T[K]
(
function whatever<T extends Record<string, (...a: any)=> any>, K extends keyof T> (someObject: T, methodName: K, ...args: Parameters<T[K]>) : ReturnType<T[K]>{
return someObject[methodName](...args);
}
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', [1])
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', ["1"]) // err
add a comment |
The option that is the type-safest one is to use Parameters
to get the parameter types of the function (in order for arguments to be type safe), ReturnType
to return the result of a function.
You also need to also add a type parameter to capture the actual key passed in so we can get the actual type of the function (T[K]
(
function whatever<T extends Record<string, (...a: any)=> any>, K extends keyof T> (someObject: T, methodName: K, ...args: Parameters<T[K]>) : ReturnType<T[K]>{
return someObject[methodName](...args);
}
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', [1])
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', ["1"]) // err
The option that is the type-safest one is to use Parameters
to get the parameter types of the function (in order for arguments to be type safe), ReturnType
to return the result of a function.
You also need to also add a type parameter to capture the actual key passed in so we can get the actual type of the function (T[K]
(
function whatever<T extends Record<string, (...a: any)=> any>, K extends keyof T> (someObject: T, methodName: K, ...args: Parameters<T[K]>) : ReturnType<T[K]>{
return someObject[methodName](...args);
}
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', [1])
whatever({ func1: (args: number) => (console.log(...args)) }, 'func1', ["1"]) // err
answered Jan 10 at 13:30


Titian Cernicova-DragomirTitian Cernicova-Dragomir
61.7k33755
61.7k33755
add a comment |
add a comment |
The closest result I can think of so far is the following
function whatever<T extends object>(object: T, methodName: keyof T, args: any) {
const func = object[methodName]
if (typeof func === "function") {
return func(...[args])
}
}
const obj = {
aValue: 1,
aFunc: (s: string) => "received: " + s
}
whatever(obj, "aFunc", "blabla")
which correctly checks the key for being part of the object.
The type inference for return type and args is still missing though. I will update the answer if I find a better solution.
add a comment |
The closest result I can think of so far is the following
function whatever<T extends object>(object: T, methodName: keyof T, args: any) {
const func = object[methodName]
if (typeof func === "function") {
return func(...[args])
}
}
const obj = {
aValue: 1,
aFunc: (s: string) => "received: " + s
}
whatever(obj, "aFunc", "blabla")
which correctly checks the key for being part of the object.
The type inference for return type and args is still missing though. I will update the answer if I find a better solution.
add a comment |
The closest result I can think of so far is the following
function whatever<T extends object>(object: T, methodName: keyof T, args: any) {
const func = object[methodName]
if (typeof func === "function") {
return func(...[args])
}
}
const obj = {
aValue: 1,
aFunc: (s: string) => "received: " + s
}
whatever(obj, "aFunc", "blabla")
which correctly checks the key for being part of the object.
The type inference for return type and args is still missing though. I will update the answer if I find a better solution.
The closest result I can think of so far is the following
function whatever<T extends object>(object: T, methodName: keyof T, args: any) {
const func = object[methodName]
if (typeof func === "function") {
return func(...[args])
}
}
const obj = {
aValue: 1,
aFunc: (s: string) => "received: " + s
}
whatever(obj, "aFunc", "blabla")
which correctly checks the key for being part of the object.
The type inference for return type and args is still missing though. I will update the answer if I find a better solution.
edited Jan 10 at 13:54
ismail simsek
2414
2414
answered Jan 10 at 13:19
Andrei ZubovAndrei Zubov
528312
528312
add a comment |
add a comment |
There you go, meets all criteria and doesn't need type assertions.
type AnyFunction = (...args: any) => any;
function whatever<
T extends Record<PropertyKey, AnyFunction>,
K extends keyof T,
A extends Parameters<T[K]>
>(object: T, methodName: K, args: A): ReturnType<T[K]> {
return object[methodName](...args);
}
The Parameters
type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any
? P
: never;
Using the PropertyKey
type instead of string
allows you to use properties of type string | number | symbol
, which is the full gamut supported by JavaScript.
add a comment |
There you go, meets all criteria and doesn't need type assertions.
type AnyFunction = (...args: any) => any;
function whatever<
T extends Record<PropertyKey, AnyFunction>,
K extends keyof T,
A extends Parameters<T[K]>
>(object: T, methodName: K, args: A): ReturnType<T[K]> {
return object[methodName](...args);
}
The Parameters
type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any
? P
: never;
Using the PropertyKey
type instead of string
allows you to use properties of type string | number | symbol
, which is the full gamut supported by JavaScript.
add a comment |
There you go, meets all criteria and doesn't need type assertions.
type AnyFunction = (...args: any) => any;
function whatever<
T extends Record<PropertyKey, AnyFunction>,
K extends keyof T,
A extends Parameters<T[K]>
>(object: T, methodName: K, args: A): ReturnType<T[K]> {
return object[methodName](...args);
}
The Parameters
type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any
? P
: never;
Using the PropertyKey
type instead of string
allows you to use properties of type string | number | symbol
, which is the full gamut supported by JavaScript.
There you go, meets all criteria and doesn't need type assertions.
type AnyFunction = (...args: any) => any;
function whatever<
T extends Record<PropertyKey, AnyFunction>,
K extends keyof T,
A extends Parameters<T[K]>
>(object: T, methodName: K, args: A): ReturnType<T[K]> {
return object[methodName](...args);
}
The Parameters
type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any
? P
: never;
Using the PropertyKey
type instead of string
allows you to use properties of type string | number | symbol
, which is the full gamut supported by JavaScript.
edited Jan 10 at 21:45
answered Jan 10 at 21:32


Karol MajewskiKarol Majewski
1,931210
1,931210
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54129289%2fcall-a-member-of-an-object-with-arguments%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
9XA WfGPV,fFrp U9,T4bt,E4nH7 jTSdCAfTi6oj,POJhhyQ4fLxB1RlxV0MRaAqWoq e cqI,RNoS0Pg,WpIgodWZSH vvnV9ItgoAYf