Generic function and index types










2















I'm looking to implement a generic function which accepts a single type argument "T" which is an object. It uses keyof T to specify the first argument to the function "property". Now the part I can't get working, I want to use the type of T[typeof property] to specify the type of the second argument to the function "value".



This gets me close to the ideal usage,



type Person = 
name: string;
age: number;
;

function filter<T>(property: keyof T, value: T[typeof property])

return `$property $value`



filter<Person>("name", 123);


With the above we get type checking for the "property" argument, correctly restricting it to only "name" or "age". Unfortunately for the value argument it accepts either a number or string, so it seems to be creating a union of all the keyof types of Person.



Any ideas?










share|improve this question


























    2















    I'm looking to implement a generic function which accepts a single type argument "T" which is an object. It uses keyof T to specify the first argument to the function "property". Now the part I can't get working, I want to use the type of T[typeof property] to specify the type of the second argument to the function "value".



    This gets me close to the ideal usage,



    type Person = 
    name: string;
    age: number;
    ;

    function filter<T>(property: keyof T, value: T[typeof property])

    return `$property $value`



    filter<Person>("name", 123);


    With the above we get type checking for the "property" argument, correctly restricting it to only "name" or "age". Unfortunately for the value argument it accepts either a number or string, so it seems to be creating a union of all the keyof types of Person.



    Any ideas?










    share|improve this question
























      2












      2








      2








      I'm looking to implement a generic function which accepts a single type argument "T" which is an object. It uses keyof T to specify the first argument to the function "property". Now the part I can't get working, I want to use the type of T[typeof property] to specify the type of the second argument to the function "value".



      This gets me close to the ideal usage,



      type Person = 
      name: string;
      age: number;
      ;

      function filter<T>(property: keyof T, value: T[typeof property])

      return `$property $value`



      filter<Person>("name", 123);


      With the above we get type checking for the "property" argument, correctly restricting it to only "name" or "age". Unfortunately for the value argument it accepts either a number or string, so it seems to be creating a union of all the keyof types of Person.



      Any ideas?










      share|improve this question














      I'm looking to implement a generic function which accepts a single type argument "T" which is an object. It uses keyof T to specify the first argument to the function "property". Now the part I can't get working, I want to use the type of T[typeof property] to specify the type of the second argument to the function "value".



      This gets me close to the ideal usage,



      type Person = 
      name: string;
      age: number;
      ;

      function filter<T>(property: keyof T, value: T[typeof property])

      return `$property $value`



      filter<Person>("name", 123);


      With the above we get type checking for the "property" argument, correctly restricting it to only "name" or "age". Unfortunately for the value argument it accepts either a number or string, so it seems to be creating a union of all the keyof types of Person.



      Any ideas?







      typescript






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 14 '18 at 11:58









      rssfrncsrssfrncs

      558215




      558215






















          2 Answers
          2






          active

          oldest

          votes


















          1














          You need to create a relation between the two parameters, after all you could potentially be passing in multiple keys, and multiple values, why would the compiler asume that value should have the type of the key specified in the property parameter.



          To create the relation you will need an extra type parameter to represent the property as a literal type



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value`


          filter<Person, "name">("name", 123); // error
          filter<Person, "age">("age", 123); // ok


          The problem with the above implementation is that you have to specify the additional type parameter since typescript does not support partial type inference. (Hopefuly it will soon be possible as this issue proposes, it's slated for January 2018 but is has been pushed back several times)



          To fix this inconvenience we can create a function that returns a function and fix the T parameter in the first call and let K be inferred in the second call



          function filter<T>() 
          return function <K extends keyof T>(property: K, value: T[K])
          return `$property $value`



          filter<Person>()("name", 123); // error
          filter<Person>()("age", 123); // ok


          Or depending on what you plan to do with filter you can leave the function with two type parameters and use the return type as the source for the inference of T:



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value` as IFiler<T>


          type IFiler<T> = string & _isFilterFor: T
          class Query<T>
          addFilter(f: IFiler<T>)




          var q = new Query<Person>();
          // T will be inferred to Person in filter because we assign in to a parameter expecting IFilter<Person>
          q.addFilter(filter('age', "")) //error
          q.addFilter(filter('age', 12)) //ok
          // Same as above but assigning to a variable:
          var f: IFiler<Person> = filter('age', 12);
          var f2: IFiler<Person> = filter('age', "12");





          share|improve this answer

























          • This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

            – rssfrncs
            Nov 15 '18 at 11:11


















          1














          This way you don't have to change your code



          type Fn<T> = <K extends keyof T>(prop: K, value: T[K]) => string;

          function filter(property: string, value: any)
          return `$property $value`;


          type Person =
          name: string;
          age: number;
          ;

          const result = (filter as Fn<Person>)("name", 123);





          share|improve this answer























          • Appreciate the alternative solution

            – rssfrncs
            Nov 15 '18 at 11:10










          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%2f53299743%2fgeneric-function-and-index-types%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          You need to create a relation between the two parameters, after all you could potentially be passing in multiple keys, and multiple values, why would the compiler asume that value should have the type of the key specified in the property parameter.



          To create the relation you will need an extra type parameter to represent the property as a literal type



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value`


          filter<Person, "name">("name", 123); // error
          filter<Person, "age">("age", 123); // ok


          The problem with the above implementation is that you have to specify the additional type parameter since typescript does not support partial type inference. (Hopefuly it will soon be possible as this issue proposes, it's slated for January 2018 but is has been pushed back several times)



          To fix this inconvenience we can create a function that returns a function and fix the T parameter in the first call and let K be inferred in the second call



          function filter<T>() 
          return function <K extends keyof T>(property: K, value: T[K])
          return `$property $value`



          filter<Person>()("name", 123); // error
          filter<Person>()("age", 123); // ok


          Or depending on what you plan to do with filter you can leave the function with two type parameters and use the return type as the source for the inference of T:



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value` as IFiler<T>


          type IFiler<T> = string & _isFilterFor: T
          class Query<T>
          addFilter(f: IFiler<T>)




          var q = new Query<Person>();
          // T will be inferred to Person in filter because we assign in to a parameter expecting IFilter<Person>
          q.addFilter(filter('age', "")) //error
          q.addFilter(filter('age', 12)) //ok
          // Same as above but assigning to a variable:
          var f: IFiler<Person> = filter('age', 12);
          var f2: IFiler<Person> = filter('age', "12");





          share|improve this answer

























          • This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

            – rssfrncs
            Nov 15 '18 at 11:11















          1














          You need to create a relation between the two parameters, after all you could potentially be passing in multiple keys, and multiple values, why would the compiler asume that value should have the type of the key specified in the property parameter.



          To create the relation you will need an extra type parameter to represent the property as a literal type



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value`


          filter<Person, "name">("name", 123); // error
          filter<Person, "age">("age", 123); // ok


          The problem with the above implementation is that you have to specify the additional type parameter since typescript does not support partial type inference. (Hopefuly it will soon be possible as this issue proposes, it's slated for January 2018 but is has been pushed back several times)



          To fix this inconvenience we can create a function that returns a function and fix the T parameter in the first call and let K be inferred in the second call



          function filter<T>() 
          return function <K extends keyof T>(property: K, value: T[K])
          return `$property $value`



          filter<Person>()("name", 123); // error
          filter<Person>()("age", 123); // ok


          Or depending on what you plan to do with filter you can leave the function with two type parameters and use the return type as the source for the inference of T:



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value` as IFiler<T>


          type IFiler<T> = string & _isFilterFor: T
          class Query<T>
          addFilter(f: IFiler<T>)




          var q = new Query<Person>();
          // T will be inferred to Person in filter because we assign in to a parameter expecting IFilter<Person>
          q.addFilter(filter('age', "")) //error
          q.addFilter(filter('age', 12)) //ok
          // Same as above but assigning to a variable:
          var f: IFiler<Person> = filter('age', 12);
          var f2: IFiler<Person> = filter('age', "12");





          share|improve this answer

























          • This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

            – rssfrncs
            Nov 15 '18 at 11:11













          1












          1








          1







          You need to create a relation between the two parameters, after all you could potentially be passing in multiple keys, and multiple values, why would the compiler asume that value should have the type of the key specified in the property parameter.



          To create the relation you will need an extra type parameter to represent the property as a literal type



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value`


          filter<Person, "name">("name", 123); // error
          filter<Person, "age">("age", 123); // ok


          The problem with the above implementation is that you have to specify the additional type parameter since typescript does not support partial type inference. (Hopefuly it will soon be possible as this issue proposes, it's slated for January 2018 but is has been pushed back several times)



          To fix this inconvenience we can create a function that returns a function and fix the T parameter in the first call and let K be inferred in the second call



          function filter<T>() 
          return function <K extends keyof T>(property: K, value: T[K])
          return `$property $value`



          filter<Person>()("name", 123); // error
          filter<Person>()("age", 123); // ok


          Or depending on what you plan to do with filter you can leave the function with two type parameters and use the return type as the source for the inference of T:



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value` as IFiler<T>


          type IFiler<T> = string & _isFilterFor: T
          class Query<T>
          addFilter(f: IFiler<T>)




          var q = new Query<Person>();
          // T will be inferred to Person in filter because we assign in to a parameter expecting IFilter<Person>
          q.addFilter(filter('age', "")) //error
          q.addFilter(filter('age', 12)) //ok
          // Same as above but assigning to a variable:
          var f: IFiler<Person> = filter('age', 12);
          var f2: IFiler<Person> = filter('age', "12");





          share|improve this answer















          You need to create a relation between the two parameters, after all you could potentially be passing in multiple keys, and multiple values, why would the compiler asume that value should have the type of the key specified in the property parameter.



          To create the relation you will need an extra type parameter to represent the property as a literal type



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value`


          filter<Person, "name">("name", 123); // error
          filter<Person, "age">("age", 123); // ok


          The problem with the above implementation is that you have to specify the additional type parameter since typescript does not support partial type inference. (Hopefuly it will soon be possible as this issue proposes, it's slated for January 2018 but is has been pushed back several times)



          To fix this inconvenience we can create a function that returns a function and fix the T parameter in the first call and let K be inferred in the second call



          function filter<T>() 
          return function <K extends keyof T>(property: K, value: T[K])
          return `$property $value`



          filter<Person>()("name", 123); // error
          filter<Person>()("age", 123); // ok


          Or depending on what you plan to do with filter you can leave the function with two type parameters and use the return type as the source for the inference of T:



          function filter<T, K extends keyof T>(property: K, value: T[K]) 
          return `$property $value` as IFiler<T>


          type IFiler<T> = string & _isFilterFor: T
          class Query<T>
          addFilter(f: IFiler<T>)




          var q = new Query<Person>();
          // T will be inferred to Person in filter because we assign in to a parameter expecting IFilter<Person>
          q.addFilter(filter('age', "")) //error
          q.addFilter(filter('age', 12)) //ok
          // Same as above but assigning to a variable:
          var f: IFiler<Person> = filter('age', 12);
          var f2: IFiler<Person> = filter('age', "12");






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 14 '18 at 12:10

























          answered Nov 14 '18 at 12:05









          Titian Cernicova-DragomirTitian Cernicova-Dragomir

          70k34866




          70k34866












          • This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

            – rssfrncs
            Nov 15 '18 at 11:11

















          • This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

            – rssfrncs
            Nov 15 '18 at 11:11
















          This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

          – rssfrncs
          Nov 15 '18 at 11:11





          This is great! Thanks for the pointer about partial type inference and the issue to keep track of.

          – rssfrncs
          Nov 15 '18 at 11:11













          1














          This way you don't have to change your code



          type Fn<T> = <K extends keyof T>(prop: K, value: T[K]) => string;

          function filter(property: string, value: any)
          return `$property $value`;


          type Person =
          name: string;
          age: number;
          ;

          const result = (filter as Fn<Person>)("name", 123);





          share|improve this answer























          • Appreciate the alternative solution

            – rssfrncs
            Nov 15 '18 at 11:10















          1














          This way you don't have to change your code



          type Fn<T> = <K extends keyof T>(prop: K, value: T[K]) => string;

          function filter(property: string, value: any)
          return `$property $value`;


          type Person =
          name: string;
          age: number;
          ;

          const result = (filter as Fn<Person>)("name", 123);





          share|improve this answer























          • Appreciate the alternative solution

            – rssfrncs
            Nov 15 '18 at 11:10













          1












          1








          1







          This way you don't have to change your code



          type Fn<T> = <K extends keyof T>(prop: K, value: T[K]) => string;

          function filter(property: string, value: any)
          return `$property $value`;


          type Person =
          name: string;
          age: number;
          ;

          const result = (filter as Fn<Person>)("name", 123);





          share|improve this answer













          This way you don't have to change your code



          type Fn<T> = <K extends keyof T>(prop: K, value: T[K]) => string;

          function filter(property: string, value: any)
          return `$property $value`;


          type Person =
          name: string;
          age: number;
          ;

          const result = (filter as Fn<Person>)("name", 123);






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 14 '18 at 16:03









          Daniel PérezDaniel Pérez

          497312




          497312












          • Appreciate the alternative solution

            – rssfrncs
            Nov 15 '18 at 11:10

















          • Appreciate the alternative solution

            – rssfrncs
            Nov 15 '18 at 11:10
















          Appreciate the alternative solution

          – rssfrncs
          Nov 15 '18 at 11:10





          Appreciate the alternative solution

          – rssfrncs
          Nov 15 '18 at 11:10

















          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%2f53299743%2fgeneric-function-and-index-types%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

          Use pre created SQLite database for Android project in kotlin

          Darth Vader #20

          Ondo