TypeScript dynamic generate value type checking












0














Suppose I have a generic function that takes raw data, and an array of parsers to generate a simple object



function parseData<T extends object>(
rawData: any,
...parsers: Array<{
field: keyof T
parser(rawData: any): any
}>
): T {
// Loop through and generate object
}


And the function call would look like



interface ResultData {
name: string,
age: number
}
const result = parseData<ResultData>(
rawData,
{ field: 'name', parser: nameParser },
{ field: 'age', parser: ageParser }
)


The type checking for field works beautifully. If I were to put in something that's not name or age, I get the expected syntax error. I am wondering if there's a way to ensure the parser result is the correct value type for that specific field. For example, I want to receive a syntax error if I try to pass { field: 'name', parser: ageParser } as one of the parsers










share|improve this question





























    0














    Suppose I have a generic function that takes raw data, and an array of parsers to generate a simple object



    function parseData<T extends object>(
    rawData: any,
    ...parsers: Array<{
    field: keyof T
    parser(rawData: any): any
    }>
    ): T {
    // Loop through and generate object
    }


    And the function call would look like



    interface ResultData {
    name: string,
    age: number
    }
    const result = parseData<ResultData>(
    rawData,
    { field: 'name', parser: nameParser },
    { field: 'age', parser: ageParser }
    )


    The type checking for field works beautifully. If I were to put in something that's not name or age, I get the expected syntax error. I am wondering if there's a way to ensure the parser result is the correct value type for that specific field. For example, I want to receive a syntax error if I try to pass { field: 'name', parser: ageParser } as one of the parsers










    share|improve this question



























      0












      0








      0







      Suppose I have a generic function that takes raw data, and an array of parsers to generate a simple object



      function parseData<T extends object>(
      rawData: any,
      ...parsers: Array<{
      field: keyof T
      parser(rawData: any): any
      }>
      ): T {
      // Loop through and generate object
      }


      And the function call would look like



      interface ResultData {
      name: string,
      age: number
      }
      const result = parseData<ResultData>(
      rawData,
      { field: 'name', parser: nameParser },
      { field: 'age', parser: ageParser }
      )


      The type checking for field works beautifully. If I were to put in something that's not name or age, I get the expected syntax error. I am wondering if there's a way to ensure the parser result is the correct value type for that specific field. For example, I want to receive a syntax error if I try to pass { field: 'name', parser: ageParser } as one of the parsers










      share|improve this question















      Suppose I have a generic function that takes raw data, and an array of parsers to generate a simple object



      function parseData<T extends object>(
      rawData: any,
      ...parsers: Array<{
      field: keyof T
      parser(rawData: any): any
      }>
      ): T {
      // Loop through and generate object
      }


      And the function call would look like



      interface ResultData {
      name: string,
      age: number
      }
      const result = parseData<ResultData>(
      rawData,
      { field: 'name', parser: nameParser },
      { field: 'age', parser: ageParser }
      )


      The type checking for field works beautifully. If I were to put in something that's not name or age, I get the expected syntax error. I am wondering if there's a way to ensure the parser result is the correct value type for that specific field. For example, I want to receive a syntax error if I try to pass { field: 'name', parser: ageParser } as one of the parsers







      typescript typescript-typings






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 20 at 18:56

























      asked Nov 20 at 18:49









      Yifei Xu

      1039




      1039
























          1 Answer
          1






          active

          oldest

          votes


















          1














          You can do something close to what you want but it involves adding type parameters which need to be inferred, and since TypeScript doesn't currently allow specifying some type paremters while allowing others to be inferred, (but it's coming soon), there's a trick you can use to sort of give that to you in the meantime.



          First, let's use a mapped type with a lookup to generate the union of possible parser types for type T, assuming the "raw data" is of type R:



          type Parsers<T, R> = { [K in keyof T]: { field: K, parser(rawData: R): T[K] } }[keyof T];


          Then we curry the parseData() function to the user-specified T value, as well as the R value corresponding to rawData, and the tuple rest parameter type P corresponding to parsers. The implementation of the function is left to you:



          declare function parseData<T extends object>(): <R, P extends Parsers<T, R>>(
          rawData: R,
          ...parsers: P
          ) => T;


          Now let's see if it works:



          interface ResultData {
          name: string,
          age: number
          }

          const resultDataParse = parseData<ResultData>(); // T is now ResultData

          // call parser:
          const result = resultDataParse(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x.length }
          );


          This looks good, and should fail if you switch things around:



          const oops = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x + "!" } // error, string not number
          )


          It will not fail if you leave out a parser:



          const noError = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          )


          so either you want the output to be Partial<T> instead of T, or you want to somehow constrain parseData() to require all parsers; but that wasn't in the question and I've got to run. 🏃‍♂️



          Hope that helps. Good luck!






          share|improve this answer





















          • Beautiful solution, I learned a lot from the examples. Thanks!
            – Yifei Xu
            Nov 21 at 0:23











          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%2f53399612%2ftypescript-dynamic-generate-value-type-checking%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          You can do something close to what you want but it involves adding type parameters which need to be inferred, and since TypeScript doesn't currently allow specifying some type paremters while allowing others to be inferred, (but it's coming soon), there's a trick you can use to sort of give that to you in the meantime.



          First, let's use a mapped type with a lookup to generate the union of possible parser types for type T, assuming the "raw data" is of type R:



          type Parsers<T, R> = { [K in keyof T]: { field: K, parser(rawData: R): T[K] } }[keyof T];


          Then we curry the parseData() function to the user-specified T value, as well as the R value corresponding to rawData, and the tuple rest parameter type P corresponding to parsers. The implementation of the function is left to you:



          declare function parseData<T extends object>(): <R, P extends Parsers<T, R>>(
          rawData: R,
          ...parsers: P
          ) => T;


          Now let's see if it works:



          interface ResultData {
          name: string,
          age: number
          }

          const resultDataParse = parseData<ResultData>(); // T is now ResultData

          // call parser:
          const result = resultDataParse(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x.length }
          );


          This looks good, and should fail if you switch things around:



          const oops = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x + "!" } // error, string not number
          )


          It will not fail if you leave out a parser:



          const noError = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          )


          so either you want the output to be Partial<T> instead of T, or you want to somehow constrain parseData() to require all parsers; but that wasn't in the question and I've got to run. 🏃‍♂️



          Hope that helps. Good luck!






          share|improve this answer





















          • Beautiful solution, I learned a lot from the examples. Thanks!
            – Yifei Xu
            Nov 21 at 0:23
















          1














          You can do something close to what you want but it involves adding type parameters which need to be inferred, and since TypeScript doesn't currently allow specifying some type paremters while allowing others to be inferred, (but it's coming soon), there's a trick you can use to sort of give that to you in the meantime.



          First, let's use a mapped type with a lookup to generate the union of possible parser types for type T, assuming the "raw data" is of type R:



          type Parsers<T, R> = { [K in keyof T]: { field: K, parser(rawData: R): T[K] } }[keyof T];


          Then we curry the parseData() function to the user-specified T value, as well as the R value corresponding to rawData, and the tuple rest parameter type P corresponding to parsers. The implementation of the function is left to you:



          declare function parseData<T extends object>(): <R, P extends Parsers<T, R>>(
          rawData: R,
          ...parsers: P
          ) => T;


          Now let's see if it works:



          interface ResultData {
          name: string,
          age: number
          }

          const resultDataParse = parseData<ResultData>(); // T is now ResultData

          // call parser:
          const result = resultDataParse(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x.length }
          );


          This looks good, and should fail if you switch things around:



          const oops = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x + "!" } // error, string not number
          )


          It will not fail if you leave out a parser:



          const noError = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          )


          so either you want the output to be Partial<T> instead of T, or you want to somehow constrain parseData() to require all parsers; but that wasn't in the question and I've got to run. 🏃‍♂️



          Hope that helps. Good luck!






          share|improve this answer





















          • Beautiful solution, I learned a lot from the examples. Thanks!
            – Yifei Xu
            Nov 21 at 0:23














          1












          1








          1






          You can do something close to what you want but it involves adding type parameters which need to be inferred, and since TypeScript doesn't currently allow specifying some type paremters while allowing others to be inferred, (but it's coming soon), there's a trick you can use to sort of give that to you in the meantime.



          First, let's use a mapped type with a lookup to generate the union of possible parser types for type T, assuming the "raw data" is of type R:



          type Parsers<T, R> = { [K in keyof T]: { field: K, parser(rawData: R): T[K] } }[keyof T];


          Then we curry the parseData() function to the user-specified T value, as well as the R value corresponding to rawData, and the tuple rest parameter type P corresponding to parsers. The implementation of the function is left to you:



          declare function parseData<T extends object>(): <R, P extends Parsers<T, R>>(
          rawData: R,
          ...parsers: P
          ) => T;


          Now let's see if it works:



          interface ResultData {
          name: string,
          age: number
          }

          const resultDataParse = parseData<ResultData>(); // T is now ResultData

          // call parser:
          const result = resultDataParse(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x.length }
          );


          This looks good, and should fail if you switch things around:



          const oops = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x + "!" } // error, string not number
          )


          It will not fail if you leave out a parser:



          const noError = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          )


          so either you want the output to be Partial<T> instead of T, or you want to somehow constrain parseData() to require all parsers; but that wasn't in the question and I've got to run. 🏃‍♂️



          Hope that helps. Good luck!






          share|improve this answer












          You can do something close to what you want but it involves adding type parameters which need to be inferred, and since TypeScript doesn't currently allow specifying some type paremters while allowing others to be inferred, (but it's coming soon), there's a trick you can use to sort of give that to you in the meantime.



          First, let's use a mapped type with a lookup to generate the union of possible parser types for type T, assuming the "raw data" is of type R:



          type Parsers<T, R> = { [K in keyof T]: { field: K, parser(rawData: R): T[K] } }[keyof T];


          Then we curry the parseData() function to the user-specified T value, as well as the R value corresponding to rawData, and the tuple rest parameter type P corresponding to parsers. The implementation of the function is left to you:



          declare function parseData<T extends object>(): <R, P extends Parsers<T, R>>(
          rawData: R,
          ...parsers: P
          ) => T;


          Now let's see if it works:



          interface ResultData {
          name: string,
          age: number
          }

          const resultDataParse = parseData<ResultData>(); // T is now ResultData

          // call parser:
          const result = resultDataParse(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x.length }
          );


          This looks good, and should fail if you switch things around:



          const oops = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          { field: 'age', parser: (x: string) => x + "!" } // error, string not number
          )


          It will not fail if you leave out a parser:



          const noError = parseData<ResultData>()(
          "hello",
          { field: 'name', parser: (x: string) => x + "!" },
          )


          so either you want the output to be Partial<T> instead of T, or you want to somehow constrain parseData() to require all parsers; but that wasn't in the question and I've got to run. 🏃‍♂️



          Hope that helps. Good luck!







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 20 at 19:29









          jcalz

          22k21738




          22k21738












          • Beautiful solution, I learned a lot from the examples. Thanks!
            – Yifei Xu
            Nov 21 at 0:23


















          • Beautiful solution, I learned a lot from the examples. Thanks!
            – Yifei Xu
            Nov 21 at 0:23
















          Beautiful solution, I learned a lot from the examples. Thanks!
          – Yifei Xu
          Nov 21 at 0:23




          Beautiful solution, I learned a lot from the examples. Thanks!
          – Yifei Xu
          Nov 21 at 0:23


















          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.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • 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%2f53399612%2ftypescript-dynamic-generate-value-type-checking%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

          Wiesbaden

          Marschland

          Dieringhausen