TypeScript: custom type guards

Type guards are tools for type narrowing in TypeScript. You may have used some of those features e.g. typeof, instanceof, in etc. already but I want to describe writing guards for types created by you.

Type Predicate

To create a custom guard we need a function that returns a type predicate, it is in the form of param is Type. There is no naming convention, but I use the guard prefix which explicitely indicates the purpose of the function (as opposed to is prefix).

In the guard function you need to make all the required assessments on incoming object and return that result.

Below is the example implementation:

interface INavigationAction {
    type: string;
    path: string;
}
const guardINavigationAction = (test: unknown): test is INavigationAction => {
    const has = (prop: keyof INavigationAction) =>
        Object.prototype.hasOwnProperty.call(test, prop);
    return typeof test === "object" 
        && !!test 
        && has('type') 
        && has('path');
}

console.log(guardINavigationAction({type:"Something"})); // false
console.log(guardINavigationAction({path:"a/path"})); // false
console.log(guardINavigationAction({type:"Something", path:"a/path"})); // true
console.log(guardINavigationAction({type:"Something", path:"a/path", extraParam: true})); // true
Code language: TypeScript (typescript)

Usage

It is handy in all those places where the type narrowing is needed, e.g when union type is used:

interface IPrintAction {
    type: string;
    orientation: 'PORTRAIT' | 'LANDSCAPE';
}

const action:IPrintAction | INavigationAction;
if(guardINavigationAction(action)) {
    // navigate somewhere
}
else {
    // print it
}
Code language: TypeScript (typescript)

but also in places where you dont know exactly what data is coming e.g. from the API.

interface IResponseData {
    prop1: number;
    prop2: string;
}
const guardIResponseData = (test: unknown): test is IResponseData => {
    const has = (prop: keyof IResponseData) =>
        Object.prototype.hasOwnProperty.call(test, prop);
    return typeof test === "object" && !!test
           && has('prop1') && has('prop2');
}

const response: Response = await fetch('api/path');
const data = await response.json(); // data has `any` type

if(guardIResponseData(data)) {
    // it is IResponseData
    console.log(data.prop1);
} else {
    // some other type
}
Code language: TypeScript (typescript)

As shown above type guards are simple yet very useful tools in the programmers arsenal.

This entry was posted in TypeScript and tagged , . Bookmark the permalink.