Call a member of an object with arguments












27















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





























    27















    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



























      27












      27








      27


      4






      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








      edited Jan 10 at 15:15









      user489872

      1,38231335




      1,38231335










      asked Jan 10 at 13:01









      Jaffa The CakeJaffa The Cake

      5,45122334




      5,45122334
























          8 Answers
          8






          active

          oldest

          votes


















          28














          I think this does the trick:



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


          Requires TS 3 though!






          share|improve this answer





















          • 1





            This doesn't quite work with generics, but probably good enough for most situations.

            – Jaffa The Cake
            Jan 10 at 13:44






          • 3





            I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

            – Jaffa The Cake
            Jan 10 at 13:48






          • 3





            Today I learned I learn?

            – Ben Kolya Mansley
            Jan 10 at 14:06






          • 2





            One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

            – Surma
            Jan 10 at 14:55






          • 3





            @BenKolyaMansley haha nbd big deal

            – Jaffa The Cake
            Jan 10 at 15:05



















          3














          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
            Jan 10 at 13:46











          • Forgot the ReturnType. Fixed

            – aleclarson
            Jan 10 at 13:47











          • It now works similarly to the accepted answer (and has the same pitfall).

            – Jaffa The Cake
            Jan 10 at 13:51



















          2














          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































            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
              Jan 10 at 13:33











            • Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

              – Andrei Zubov
              Jan 10 at 13:38











            • This has the same pitfall as the accepted answer.

              – Jaffa The Cake
              Jan 10 at 13:47



















            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
              Jan 10 at 13:16



















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

































                0














                There you go, meets all criteria and doesn't need type assertions.



                type AnyFunction = (...args: any) => any;

                function whatever<
                T extends Record<PropertyKey, AnyFunction>,
                K extends keyof T,
                A extends Parameters<T[K]>
                >(object: T, methodName: K, args: A): ReturnType<T[K]> {
                return object[methodName](...args);
                }


                The Parameters type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:



                type Parameters<T extends (...args: any) => any> =
                T extends (...args: infer P) => any
                ? P
                : never;


                Using the PropertyKey type instead of string allows you to use properties of type string | number | symbol, which is the full gamut supported by JavaScript.






                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%2fcall-a-member-of-an-object-with-arguments%23new-answer', 'question_page');
                  }
                  );

                  Post as a guest















                  Required, but never shown

























                  8 Answers
                  8






                  active

                  oldest

                  votes








                  8 Answers
                  8






                  active

                  oldest

                  votes









                  active

                  oldest

                  votes






                  active

                  oldest

                  votes









                  28














                  I think this does the trick:



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


                  Requires TS 3 though!






                  share|improve this answer





















                  • 1





                    This doesn't quite work with generics, but probably good enough for most situations.

                    – Jaffa The Cake
                    Jan 10 at 13:44






                  • 3





                    I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

                    – Jaffa The Cake
                    Jan 10 at 13:48






                  • 3





                    Today I learned I learn?

                    – Ben Kolya Mansley
                    Jan 10 at 14:06






                  • 2





                    One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

                    – Surma
                    Jan 10 at 14:55






                  • 3





                    @BenKolyaMansley haha nbd big deal

                    – Jaffa The Cake
                    Jan 10 at 15:05
















                  28














                  I think this does the trick:



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


                  Requires TS 3 though!






                  share|improve this answer





















                  • 1





                    This doesn't quite work with generics, but probably good enough for most situations.

                    – Jaffa The Cake
                    Jan 10 at 13:44






                  • 3





                    I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

                    – Jaffa The Cake
                    Jan 10 at 13:48






                  • 3





                    Today I learned I learn?

                    – Ben Kolya Mansley
                    Jan 10 at 14:06






                  • 2





                    One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

                    – Surma
                    Jan 10 at 14:55






                  • 3





                    @BenKolyaMansley haha nbd big deal

                    – Jaffa The Cake
                    Jan 10 at 15:05














                  28












                  28








                  28







                  I think this does the trick:



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


                  Requires TS 3 though!






                  share|improve this answer















                  I think this does the trick:



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


                  Requires TS 3 though!







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Jan 10 at 15:28









                  Surma

                  835




                  835










                  answered Jan 10 at 13:31









                  richsilvrichsilv

                  7,60811627




                  7,60811627








                  • 1





                    This doesn't quite work with generics, but probably good enough for most situations.

                    – Jaffa The Cake
                    Jan 10 at 13:44






                  • 3





                    I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

                    – Jaffa The Cake
                    Jan 10 at 13:48






                  • 3





                    Today I learned I learn?

                    – Ben Kolya Mansley
                    Jan 10 at 14:06






                  • 2





                    One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

                    – Surma
                    Jan 10 at 14:55






                  • 3





                    @BenKolyaMansley haha nbd big deal

                    – Jaffa The Cake
                    Jan 10 at 15:05














                  • 1





                    This doesn't quite work with generics, but probably good enough for most situations.

                    – Jaffa The Cake
                    Jan 10 at 13:44






                  • 3





                    I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

                    – Jaffa The Cake
                    Jan 10 at 13:48






                  • 3





                    Today I learned I learn?

                    – Ben Kolya Mansley
                    Jan 10 at 14:06






                  • 2





                    One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

                    – Surma
                    Jan 10 at 14:55






                  • 3





                    @BenKolyaMansley haha nbd big deal

                    – Jaffa The Cake
                    Jan 10 at 15:05








                  1




                  1





                  This doesn't quite work with generics, but probably good enough for most situations.

                  – Jaffa The Cake
                  Jan 10 at 13:44





                  This doesn't quite work with generics, but probably good enough for most situations.

                  – Jaffa The Cake
                  Jan 10 at 13:44




                  3




                  3





                  I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

                  – Jaffa The Cake
                  Jan 10 at 13:48





                  I should also say: Thank you! TIL I learn Parameters<F>, ReturnType<F>, and the use of m in M.

                  – Jaffa The Cake
                  Jan 10 at 13:48




                  3




                  3





                  Today I learned I learn?

                  – Ben Kolya Mansley
                  Jan 10 at 14:06





                  Today I learned I learn?

                  – Ben Kolya Mansley
                  Jan 10 at 14:06




                  2




                  2





                  One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

                  – Surma
                  Jan 10 at 14:55





                  One comment: <M extends keyof T, ... makes sure that methodName is actually a key of obj :)

                  – Surma
                  Jan 10 at 14:55




                  3




                  3





                  @BenKolyaMansley haha nbd big deal

                  – Jaffa The Cake
                  Jan 10 at 15:05





                  @BenKolyaMansley haha nbd big deal

                  – Jaffa The Cake
                  Jan 10 at 15:05













                  3














                  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
                    Jan 10 at 13:46











                  • Forgot the ReturnType. Fixed

                    – aleclarson
                    Jan 10 at 13:47











                  • It now works similarly to the accepted answer (and has the same pitfall).

                    – Jaffa The Cake
                    Jan 10 at 13:51
















                  3














                  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
                    Jan 10 at 13:46











                  • Forgot the ReturnType. Fixed

                    – aleclarson
                    Jan 10 at 13:47











                  • It now works similarly to the accepted answer (and has the same pitfall).

                    – Jaffa The Cake
                    Jan 10 at 13:51














                  3












                  3








                  3







                  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 Jan 10 at 13:47

























                  answered Jan 10 at 13:44









                  aleclarsonaleclarson

                  10k64064




                  10k64064













                  • The return type comes back as any.

                    – Jaffa The Cake
                    Jan 10 at 13:46











                  • Forgot the ReturnType. Fixed

                    – aleclarson
                    Jan 10 at 13:47











                  • It now works similarly to the accepted answer (and has the same pitfall).

                    – Jaffa The Cake
                    Jan 10 at 13:51



















                  • The return type comes back as any.

                    – Jaffa The Cake
                    Jan 10 at 13:46











                  • Forgot the ReturnType. Fixed

                    – aleclarson
                    Jan 10 at 13:47











                  • It now works similarly to the accepted answer (and has the same pitfall).

                    – Jaffa The Cake
                    Jan 10 at 13:51

















                  The return type comes back as any.

                  – Jaffa The Cake
                  Jan 10 at 13:46





                  The return type comes back as any.

                  – Jaffa The Cake
                  Jan 10 at 13:46













                  Forgot the ReturnType. Fixed

                  – aleclarson
                  Jan 10 at 13:47





                  Forgot the ReturnType. Fixed

                  – aleclarson
                  Jan 10 at 13:47













                  It now works similarly to the accepted answer (and has the same pitfall).

                  – Jaffa The Cake
                  Jan 10 at 13:51





                  It now works similarly to the accepted answer (and has the same pitfall).

                  – Jaffa The Cake
                  Jan 10 at 13:51











                  2














                  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




























                    2














                    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


























                      2












                      2








                      2







                      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 Jan 10 at 13:17









                      Karl MerkliKarl Merkli

                      283




                      283























                          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
                            Jan 10 at 13:33











                          • Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

                            – Andrei Zubov
                            Jan 10 at 13:38











                          • This has the same pitfall as the accepted answer.

                            – Jaffa The Cake
                            Jan 10 at 13:47
















                          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
                            Jan 10 at 13:33











                          • Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

                            – Andrei Zubov
                            Jan 10 at 13:38











                          • This has the same pitfall as the accepted answer.

                            – Jaffa The Cake
                            Jan 10 at 13:47














                          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 Jan 10 at 13:32









                          David KhourshidDavid Khourshid

                          1962




                          1962













                          • Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417

                            – morkro
                            Jan 10 at 13:33











                          • Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

                            – Andrei Zubov
                            Jan 10 at 13:38











                          • This has the same pitfall as the accepted answer.

                            – Jaffa The Cake
                            Jan 10 at 13:47



















                          • Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417

                            – morkro
                            Jan 10 at 13:33











                          • Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

                            – Andrei Zubov
                            Jan 10 at 13:38











                          • This has the same pitfall as the accepted answer.

                            – Jaffa The Cake
                            Jan 10 at 13:47

















                          Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417

                          – morkro
                          Jan 10 at 13:33





                          Please link the source of the code snippet: twitter.com/DasSurma/status/1083352705475772417

                          – morkro
                          Jan 10 at 13:33













                          Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

                          – Andrei Zubov
                          Jan 10 at 13:38





                          Great example! it seems to be working correctly, even though I get a compiler warning for this line: ```): T[TKey] extends ((...args: TArgs) => unknown) ? ReturnType<T[TKey]> : never {````

                          – Andrei Zubov
                          Jan 10 at 13:38













                          This has the same pitfall as the accepted answer.

                          – Jaffa The Cake
                          Jan 10 at 13:47





                          This has the same pitfall as the accepted answer.

                          – Jaffa The Cake
                          Jan 10 at 13:47











                          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
                            Jan 10 at 13:16
















                          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
                            Jan 10 at 13:16














                          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 Jan 10 at 13:09









                          Dmase05Dmase05

                          7641017




                          7641017













                          • That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.

                            – Andrei Zubov
                            Jan 10 at 13:16



















                          • That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.

                            – Andrei Zubov
                            Jan 10 at 13:16

















                          That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.

                          – Andrei Zubov
                          Jan 10 at 13:16





                          That, unfortunately, doesn't infer the types of function arguments and the return type. Also won't work if you have object with mixed function and value fields.

                          – Andrei Zubov
                          Jan 10 at 13:16











                          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 Jan 10 at 13:30









                              Titian Cernicova-DragomirTitian Cernicova-Dragomir

                              61.7k33755




                              61.7k33755























                                  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 Jan 10 at 13:54









                                      ismail simsek

                                      2414




                                      2414










                                      answered Jan 10 at 13:19









                                      Andrei ZubovAndrei Zubov

                                      528312




                                      528312























                                          0














                                          There you go, meets all criteria and doesn't need type assertions.



                                          type AnyFunction = (...args: any) => any;

                                          function whatever<
                                          T extends Record<PropertyKey, AnyFunction>,
                                          K extends keyof T,
                                          A extends Parameters<T[K]>
                                          >(object: T, methodName: K, args: A): ReturnType<T[K]> {
                                          return object[methodName](...args);
                                          }


                                          The Parameters type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:



                                          type Parameters<T extends (...args: any) => any> =
                                          T extends (...args: infer P) => any
                                          ? P
                                          : never;


                                          Using the PropertyKey type instead of string allows you to use properties of type string | number | symbol, which is the full gamut supported by JavaScript.






                                          share|improve this answer






























                                            0














                                            There you go, meets all criteria and doesn't need type assertions.



                                            type AnyFunction = (...args: any) => any;

                                            function whatever<
                                            T extends Record<PropertyKey, AnyFunction>,
                                            K extends keyof T,
                                            A extends Parameters<T[K]>
                                            >(object: T, methodName: K, args: A): ReturnType<T[K]> {
                                            return object[methodName](...args);
                                            }


                                            The Parameters type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:



                                            type Parameters<T extends (...args: any) => any> =
                                            T extends (...args: infer P) => any
                                            ? P
                                            : never;


                                            Using the PropertyKey type instead of string allows you to use properties of type string | number | symbol, which is the full gamut supported by JavaScript.






                                            share|improve this answer




























                                              0












                                              0








                                              0







                                              There you go, meets all criteria and doesn't need type assertions.



                                              type AnyFunction = (...args: any) => any;

                                              function whatever<
                                              T extends Record<PropertyKey, AnyFunction>,
                                              K extends keyof T,
                                              A extends Parameters<T[K]>
                                              >(object: T, methodName: K, args: A): ReturnType<T[K]> {
                                              return object[methodName](...args);
                                              }


                                              The Parameters type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:



                                              type Parameters<T extends (...args: any) => any> =
                                              T extends (...args: infer P) => any
                                              ? P
                                              : never;


                                              Using the PropertyKey type instead of string allows you to use properties of type string | number | symbol, which is the full gamut supported by JavaScript.






                                              share|improve this answer















                                              There you go, meets all criteria and doesn't need type assertions.



                                              type AnyFunction = (...args: any) => any;

                                              function whatever<
                                              T extends Record<PropertyKey, AnyFunction>,
                                              K extends keyof T,
                                              A extends Parameters<T[K]>
                                              >(object: T, methodName: K, args: A): ReturnType<T[K]> {
                                              return object[methodName](...args);
                                              }


                                              The Parameters type is a part of the standard library since TypeScript 3.1. If you're using an older version, create it yourself:



                                              type Parameters<T extends (...args: any) => any> =
                                              T extends (...args: infer P) => any
                                              ? P
                                              : never;


                                              Using the PropertyKey type instead of string allows you to use properties of type string | number | symbol, which is the full gamut supported by JavaScript.







                                              share|improve this answer














                                              share|improve this answer



                                              share|improve this answer








                                              edited Jan 10 at 21:45

























                                              answered Jan 10 at 21:32









                                              Karol MajewskiKarol Majewski

                                              1,931210




                                              1,931210






























                                                  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%2fcall-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

                                                  Human spaceflight

                                                  Can not write log (Is /dev/pts mounted?) - openpty in Ubuntu-on-Windows?

                                                  File:DeusFollowingSea.jpg