Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: User-Defined type guards for literal types?

Tags:

typescript

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...

like image 376
SumNeuron Avatar asked Apr 25 '19 13:04

SumNeuron


People also ask

Which operator is used as type guard in TypeScript?

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.

What is a literal type in TypeScript?

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.

How do you narrow down TypeScript?

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.


1 Answers

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!

like image 84
jcalz Avatar answered Oct 21 '22 19:10

jcalz