TypeScript: call a member of an object with arguments












9














function whatever(object, methodName, args) {
return object[methodName](...args);
}


Can the above be typed so the the following is enforced:





  • methodName is a key of object.


  • object[methodName] is callable and its args are ...args.

  • The return type of whatever(object, methodName, args) is the return type of object[methodName](...args).


The closest thing I could find is the definition of function.apply, but it isn't quite the same as the above.










share|improve this question



























    9














    function whatever(object, methodName, args) {
    return object[methodName](...args);
    }


    Can the above be typed so the the following is enforced:





    • methodName is a key of object.


    • object[methodName] is callable and its args are ...args.

    • The return type of whatever(object, methodName, args) is the return type of object[methodName](...args).


    The closest thing I could find is the definition of function.apply, but it isn't quite the same as the above.










    share|improve this question

























      9












      9








      9







      function whatever(object, methodName, args) {
      return object[methodName](...args);
      }


      Can the above be typed so the the following is enforced:





      • methodName is a key of object.


      • object[methodName] is callable and its args are ...args.

      • The return type of whatever(object, methodName, args) is the return type of object[methodName](...args).


      The closest thing I could find is the definition of function.apply, but it isn't quite the same as the above.










      share|improve this question













      function whatever(object, methodName, args) {
      return object[methodName](...args);
      }


      Can the above be typed so the the following is enforced:





      • methodName is a key of object.


      • object[methodName] is callable and its args are ...args.

      • The return type of whatever(object, methodName, args) is the return type of object[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






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked 1 hour ago









      Jaffa The CakeJaffa The Cake

      5,26322231




      5,26322231
























          7 Answers
          7






          active

          oldest

          votes


















          9














          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!






          share|improve this answer





















          • 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 learn Parameters<F>, ReturnType<F>, and the use of m 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



















          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]);





          share|improve this answer





















          • 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



















          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])





          share|improve this answer





















          • 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



















          1














          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.






          share|improve this answer





























            1














            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





            share|improve this answer





























              1














              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? ✅






              share|improve this answer























              • The return type comes back as any.
                – Jaffa The Cake
                55 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



















              1














              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.




              share|improve this answer























                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
                });


                }
                });














                draft saved

                draft discarded


















                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









                9














                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!






                share|improve this answer





















                • 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 learn Parameters<F>, ReturnType<F>, and the use of m 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
















                9














                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!






                share|improve this answer





















                • 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 learn Parameters<F>, ReturnType<F>, and the use of m 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














                9












                9








                9






                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!






                share|improve this answer












                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!







                share|improve this answer












                share|improve this answer



                share|improve this answer










                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 learn Parameters<F>, ReturnType<F>, and the use of m 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






                • 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 learn Parameters<F>, ReturnType<F>, and the use of m 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













                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]);





                share|improve this answer





















                • 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
















                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]);





                share|improve this answer





















                • 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














                2












                2








                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]);





                share|improve this answer












                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]);






                share|improve this answer












                share|improve this answer



                share|improve this answer










                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


















                • 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











                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])





                share|improve this answer





















                • 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
















                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])





                share|improve this answer





















                • 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














                1












                1








                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])





                share|improve this answer












                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])






                share|improve this answer












                share|improve this answer



                share|improve this answer










                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


















                • 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











                1














                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.






                share|improve this answer


























                  1














                  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.






                  share|improve this answer
























                    1












                    1








                    1






                    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.






                    share|improve this answer












                    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.







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered 1 hour ago









                    Karl MerkliKarl Merkli

                    183




                    183























                        1














                        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





                        share|improve this answer


























                          1














                          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





                          share|improve this answer
























                            1












                            1








                            1






                            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





                            share|improve this answer












                            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






                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered 1 hour ago









                            Titian Cernicova-DragomirTitian Cernicova-Dragomir

                            58.7k33553




                            58.7k33553























                                1














                                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? ✅






                                share|improve this answer























                                • The return type comes back as any.
                                  – Jaffa The Cake
                                  55 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
















                                1














                                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? ✅






                                share|improve this answer























                                • The return type comes back as any.
                                  – Jaffa The Cake
                                  55 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














                                1












                                1








                                1






                                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? ✅






                                share|improve this answer














                                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? ✅







                                share|improve this answer














                                share|improve this answer



                                share|improve this answer








                                edited 54 mins ago

























                                answered 58 mins ago









                                aleclarsonaleclarson

                                9,88564062




                                9,88564062












                                • The return type comes back as any.
                                  – Jaffa The Cake
                                  55 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


















                                • The return type comes back as any.
                                  – Jaffa The Cake
                                  55 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
















                                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











                                1














                                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.




                                share|improve this answer




























                                  1














                                  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.




                                  share|improve this answer


























                                    1












                                    1








                                    1






                                    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.




                                    share|improve this answer














                                    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.





                                    share|improve this answer














                                    share|improve this answer



                                    share|improve this answer








                                    edited 47 mins ago









                                    ismail simsek

                                    2414




                                    2414










                                    answered 1 hour ago









                                    Andrei ZubovAndrei Zubov

                                    528312




                                    528312






























                                        draft saved

                                        draft discarded




















































                                        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.




                                        draft saved


                                        draft discarded














                                        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





















































                                        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







                                        Popular posts from this blog

                                        Hivernacle

                                        Fluorita

                                        Hulsita