TypeScript: call a member of an object with arguments
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
asked 1 hour ago
Jaffa The CakeJaffa The Cake
5,26322231
5,26322231
add a comment |
add a comment |
7 Answers
7
active
oldest
votes
I think this does the trick:
function callMethodWithArgs<
M extends string,
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!
Can confirm that this works :)
– Andrei Zubov
1 hour ago
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
2
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
53 mins ago
1
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
1
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
|
show 1 more 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
1 hour ago
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
1 hour ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
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
1 hour ago
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 |
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 |
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
55 mins ago
Forgot theReturnType
. Fixed
– aleclarson
54 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
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 |
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%2ftypescript-call-a-member-of-an-object-with-arguments%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
7 Answers
7
active
oldest
votes
7 Answers
7
active
oldest
votes
active
oldest
votes
active
oldest
votes
I think this does the trick:
function callMethodWithArgs<
M extends string,
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!
Can confirm that this works :)
– Andrei Zubov
1 hour ago
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
2
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
53 mins ago
1
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
1
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
|
show 1 more comment
I think this does the trick:
function callMethodWithArgs<
M extends string,
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!
Can confirm that this works :)
– Andrei Zubov
1 hour ago
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
2
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
53 mins ago
1
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
1
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
|
show 1 more comment
I think this does the trick:
function callMethodWithArgs<
M extends string,
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 string,
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!
answered 1 hour ago
richsilvrichsilv
7,40811525
7,40811525
Can confirm that this works :)
– Andrei Zubov
1 hour ago
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
2
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
53 mins ago
1
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
1
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
|
show 1 more comment
Can confirm that this works :)
– Andrei Zubov
1 hour ago
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
2
I should also say: Thank you! TIL I learnParameters<F>
,ReturnType<F>
, and the use ofm in M
.
– Jaffa The Cake
53 mins ago
1
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
1
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
Can confirm that this works :)
– Andrei Zubov
1 hour ago
Can confirm that this works :)
– Andrei Zubov
1 hour ago
1
1
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
This doesn't quite work with generics, but probably good enough for most situations.
– Jaffa The Cake
57 mins ago
2
2
I should also say: Thank you! TIL I learn
Parameters<F>
, ReturnType<F>
, and the use of m in M
.– Jaffa The Cake
53 mins ago
I should also say: Thank you! TIL I learn
Parameters<F>
, ReturnType<F>
, and the use of m in M
.– Jaffa The Cake
53 mins ago
1
1
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
Today I learned I learn?
– Ben Kolya Mansley
35 mins ago
1
1
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
Ah yes, hadn't thought about generics in the methods themselves. Sorry it's a bit terse - was going to provide some examples, but... lunch.
– richsilv
31 mins ago
|
show 1 more 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
1 hour ago
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
1 hour ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
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
1 hour ago
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
1 hour ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
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 1 hour ago
David KhourshidDavid Khourshid
1962
1962
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
1 hour ago
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
1 hour ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
add a comment |
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
1 hour ago
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
1 hour ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
1 hour ago
Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417
– morkro
1 hour ago
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
1 hour ago
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
1 hour ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
This has the same pitfall as the accepted answer.
– Jaffa The Cake
54 mins ago
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
1 hour ago
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
1 hour ago
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 1 hour ago
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
1 hour ago
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
1 hour ago
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
1 hour ago
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
1 hour ago
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 1 hour ago
Karl MerkliKarl Merkli
183
183
add a comment |
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 1 hour ago
Titian Cernicova-DragomirTitian Cernicova-Dragomir
58.7k33553
58.7k33553
add a comment |
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
55 mins ago
Forgot theReturnType
. Fixed
– aleclarson
54 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
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
55 mins ago
Forgot theReturnType
. Fixed
– aleclarson
54 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
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 54 mins ago
answered 58 mins ago
aleclarsonaleclarson
9,88564062
9,88564062
The return type comes back asany
.
– Jaffa The Cake
55 mins ago
Forgot theReturnType
. Fixed
– aleclarson
54 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
add a comment |
The return type comes back asany
.
– Jaffa The Cake
55 mins ago
Forgot theReturnType
. Fixed
– aleclarson
54 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
The return type comes back as
any
.– Jaffa The Cake
55 mins ago
The return type comes back as
any
.– Jaffa The Cake
55 mins ago
Forgot the
ReturnType
. Fixed– aleclarson
54 mins ago
Forgot the
ReturnType
. Fixed– aleclarson
54 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
It now works similarly to the accepted answer (and has the same pitfall).
– Jaffa The Cake
51 mins ago
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 47 mins ago
ismail simsek
2414
2414
answered 1 hour ago
Andrei ZubovAndrei Zubov
528312
528312
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%2ftypescript-call-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