TypeScript dynamic generate value type checking
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
add a comment |
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
add a comment |
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
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
typescript typescript-typings
edited Nov 20 at 18:56
asked Nov 20 at 18:49
Yifei Xu
1039
1039
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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!
Beautiful solution, I learned a lot from the examples. Thanks!
– Yifei Xu
Nov 21 at 0:23
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%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
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!
Beautiful solution, I learned a lot from the examples. Thanks!
– Yifei Xu
Nov 21 at 0:23
add a comment |
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!
Beautiful solution, I learned a lot from the examples. Thanks!
– Yifei Xu
Nov 21 at 0:23
add a comment |
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!
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!
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
add a comment |
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
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53399612%2ftypescript-dynamic-generate-value-type-checking%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown