Note: I'm new to typescript. Prior to posting I read the docs regarding advance types and type guards. In addition I also read several related S.O. question (e.g. user defined type guards [typescript], and How to write a user defined type guard for "string" | "literal" | "types"?)
Most similar to my question is the later question where you might have some custom type on a literal (in this instance string
, but the solution should apply to number
as well) e.g.
type Format = 'JSON' | 'CSV' | 'XML'
In the second question the user asks in regards to a solution of typescript's keyof
key word and @Ryan Cavanaugh's answer goes about this via changing the type from a literal
to an interface
and checking the keys of the interface:
// copy-pasted from answer for convenience
interface McuParams {
foo, bar, baz;
}
function isKeyOfMcuParams(x: string): x is keyof McuParams {
switch (x) {
case 'foo':
case 'bar':
case 'baz':
return true;
default:
return false;
}
}
My question is specifically if there is a way to do user defined type guards using the type itself e.g.
const isFormat = (maybe:String|Format): maybe is Format => /* something goes here */
To my knowledge the following does not work (replacing just /* something goes here */
):
// 1
/*
* As stated in the docs "The right side of the instanceof needs to
* be a constructor function" but we have a literal
*/
maybe instaceof Format
//2
/* As stated in the docs "typename" must be "number",
* "string", "boolean", or "symbol"
*/
typeof maybe === 'format'
//3
/* no idea */
(<Format>maybe)
So is @Ryan Cavanaugh's answer the only viable solution? It seems extremely verbose...
TypeScript comes with some built-in type guards: typeof and instanceof . They're very useful, but have limited scope. For example, typeof can only be used to check string , number , bigint , function , boolean , symbol , object , and undefined types.
The string literal type allows you to specify a set of possible string values for a variable, only those string values can be assigned to a variable. TypeScript throws a compile-time error if one tries to assign a value to the variable that isn't defined by the string literal type.
The in operator narrowing JavaScript has an operator for determining if an object has a property with a name: the in operator. TypeScript takes this into account as a way to narrow down potential types. For example, with the code: "value" in x . where "value" is a string literal and x is a union type.
The best way to do this is to derive the type Format
from a value like an array which contains all of the Format
literals. There are a number of ways to do this. I will show the easiest way assuming you are using TS3.4+:
const formats = ['JSON', 'CSV', 'XML'] as const;
type Format = typeof formats[number];
You can verify that Format
is the same as before. Now, armed with an array, you can use it to build a type guard:
function isFormat(x: string): x is Format {
// widen formats to string[] so indexOf(x) works
return (formats as readonly string[]).indexOf(x) >= 0;
}
That should work as expected and not be too verbose (or at least it doesn't repeat string literals anywhere). Hope that helps; good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With