 
 TypeScript Type Guards
Introduction
In TypeScript, type-safety is typically enforced by using data that we have created. For example,
we may define a type Foo and pass it as a parameter to another function. TypeScript will complain
if we try to construct Foo in an invalid way. Here are a few examples:
type Foo = string;
const f: Foo = 3; // ❌ Cannot assign a `number` to a `string`type Foo = { 
    bar: number;
    baz: boolean;
};
function doSomethingWithFoo(foo: Foo) {
    // ...
}
doSomethingWithFoo({ bar: 3 }); // ❌ Missing property `baz` on `Foo`This is awesome for ensuring type safety! However, what happens if we work with data that we know nothing about?
This data may come from an API, another part of our app, or a JavaScript library without TypeScript compatibility.
How can we use this data? One solution is to use the unknown or any keywords, but these destroy our type safety
and if left unchecked will spread like cancer through our application. Quickly, every function that has to work with
external data will be accepting any or unknown. These functions will either be extremely repetitive, verifying the
data every time they are invoked, or will have zero type safety. Neither outcome sounds ideal.
TypeScript gives us a mechanism called ‘type guards’ to safely validate and narrow unknown data.
What are Type Guards?
Type guards are TypeScript’s way of validating unknown data is of a certain type. We might have something as simple
as checking if an unknown type is a number, or something as complex as checking that it is an object with many
deeply nested properties. These guards return a boolean value that states whether the unknown value is a certain
type or not. The best part is that TypeScript understands this through a process called “type narrowing” and will
automatically treat that unknown value as the specified type in all code paths that follow the guard.
Built-in Type Guards
TypeScript has built-in type guards that allow us to validate basic things. This is done in a variety of ways, so I will provide a list of examples:
if (typeof value === 'number') { /* value is number */ }
if (value instanceof Date) { /* value is Date */ }
if (Array.isArray(value)) { /* value is an array */ }
if ('id' in user) { /* user has an id property */ }Custom Type Guards
These guards serve as building blocks for custom type guards. Custom type guards are defined as a function that
returns a boolean value. However, instead of writing boolean as the return type, we write {input} is {desired type}.
This is called a type predicate. For example, to validate that a value v is a number, we would return v is number
from our guard. I prefer to create custom type guards that wrap the primitive guards so everything has a similar
interface. Here is an example from a recent project:
export function isNumber(v: unknown): v is number {
    return typeof v === "number";
}
export function isArray(v: unknown): v is Array<unknown> {
    return Array.isArray(v);
}We can use type guards to validate our own schemas too! Here is an example of how we can do this:
export interface Exercise {
    id: string;
    name: string;
}
export function isRecord<T extends string | number | symbol>(v: unknown): v is Record<T, unknown> {
    return typeof v === "object" && v !== null;
}
export function hasKeys<T extends string | number>(record: Record<T, unknown>, keys: readonly T[]): boolean {
    // Check that all specified keys are present in the record
    for (const key of keys) {
        if (!(key in record)) {
            return false;
        }
    }
    // Check that there are no extra keys in the record
    if (Object.keys(record).length !== keys.length) {
        return false;
    }
    return true;
}
export function isUUID(v: unknown): v is string {
    if (!isString(v)) {
        return false;
    }
    const regExp = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    return regExp.test(v);
}
export function isString(v: unknown): v is string {
    return typeof v === 'string';
}
export function isExercise(v: unknown): v is Exercise {
    if (!isRecord(v)) {
        return false;
    }
    const requiredKeys = ["id", "name"] as const;
    if (!hasKeys(v, requiredKeys)) {
        return false;
    }
    const {id, name} = v;
    return isUUID(id) && isString(name);
}
Additionally, we can validate enum types. I prefer not to actually use the enum keyword, so I use type unions.
export type WeightUnit = 'KG' | 'LB';
export function isWeightUnit(v: unknown): v is WeightUnit {
    return v === 'KG' || v === 'LB';
}Testing
It is worth noting that type safety in these custom comparisons is lacking. For example, we could omit or miss any
of the keys on the Exercise, or could compare the WeightUnit against 'KGS' instead of 'KG', and TypeScript
wouldn’t complain.
For this reason, I highly recommend testing your type guards. These tests are easy to write, and are simple enough for an AI assistant to do most of the lifting. If your types change without the guards being updated, it is unlikely that TypeScript will stop you, but you will run into problems in your actual application.
Conclusion
In conclusion, TypeScript’s type guards are an excellent tool for validating unknown data. They allow us to consolidate validation code into one location and maintain type safety throughout our application.
Thanks! ❤️