Is it possible in TypeScript to implement a generic function that automatically converts a string value to a primitive type specified as the generic function's type argument?
Basically I would like to do something like this:
const v1: boolean = getValue<boolean>("false"); // returns false
const v2: number = getValue<number>("2345"); // returns 2345
const v3: string = getValue<string>("2345"); // returns "2345"
const v4: number = getValue<number>("false"); // throws an error
CodePudding user response:
type Primitive = number | boolean | string | null | undefined;
function getValue<T extends Primitive, U extends `${T}` = `${T}`>(value: U):
U extends `${number}` ? number :
U extends `${boolean}` ? boolean :
U extends `${null}` ? null :
U extends `${undefined}` ? undefined :
string extends T ? Primitive :
string {
// implementation
}
Note, you do not need to specify T
, and usually shouldn’t. (Definitely don’t specify U
.) You should just write getValue("1234")
, and it will return number
. But if you do write getValue<number>("true")
, you will get an error, because "true"
is not a valid `${T}`
, that is, not a valid `${number}`
.
Bear in mind that this is only useful if the string values are known (or limited to a specific subset) at compile time. If you pass in a string
, you won’t be able to tell what you’ll get out, which is why my penultimate case just repeats all the possible options, because there’s no way to know what the string actually holds (if you pass in a known string, that we can tell is not a number, boolean, null, or undefined, that’ll just get returned as string
, which I presume is what you would do).
One other useful feature is that this handles type unions gracefully. If you have (val: '1324' | 'false') => getValue(val)
, it will return number | boolean
.
CodePudding user response:
Instead, since Typescript erases types at compile time, you should instead specify the type as a parameter. I will leave the JS implementation up to you, and more specifically focus on getting the return type correctly.
Notably, you can't get an inferenced return value, specifically like false
or "2345"
, this is just a limitation of how we infer the values.
// bigInt, Symbol, not given, but can be supported
const getValue = <T extends boolean | string | number, U extends `${T}`>(
s: U,
t: T extends string ? "string"
: T extends number ? "number"
: T extends boolean ? "boolean"
: never
): T => {
return null! // Implementation left up to you
}
const v1: boolean = getValue("false", "boolean"); // returns false
const v2: number = getValue("2345", "number"); // returns 2345
const v3: string = getValue("2345", "string"); // returns "2345"
const v4: number = getValue("false", "number"); // throws an error
View this on TS Playground