Type Predicates

Published on

The Problem

Let's say you have an array of mixed types, and you want to filter it down to just the strings:

const mixed = [1, 'hello', true, 'world', 42]; const strings = mixed.filter(item => typeof item === 'string'); // Type: (string | number | boolean)[]

If you check the type of strings, you'll see that it's (string | number | boolean)[]. TypeScript isn't smart enough to know that the filter function has narrowed the type of the array.

The Solution

You can define a function that returns a type predicate, which tells the TypeScript compiler that if the function returns true, the value is of a certain type:

function isString(value: unknown): value is string { return typeof value === 'string'; } const mixed = [1, 'hello', true, 'world', 42]; const strings = mixed.filter(isString); // string[]

Now, the type of strings is string[], which is exactly what we want.

Usage

Filtering Arrays

function isNumber(value: unknown): value is number { return typeof value === 'number'; } const mixed = [1, 'hello', true, 'world', 42]; const numbers = mixed.filter(isNumber); // number[]

Checking Object Properties

interface User { name: string; email: string; } function isUser(obj: unknown): obj is User { return ( typeof obj === 'object' && obj !== null && 'name' in obj && 'email' in obj && typeof obj.name === 'string' && typeof obj.email === 'string' ); } const data: unknown = { name: 'John', email: 'john@example.com' }; if (isUser(data)) { console.log(data.name); // TypeScript knows data is User console.log(data.email); }

Narrowing Union Types

type Cat = { type: 'cat'; meow: () => void }; type Dog = { type: 'dog'; bark: () => void }; type Animal = Cat | Dog; function isCat(animal: Animal): animal is Cat { return animal.type === 'cat'; } function handleAnimal(animal: Animal) { if (isCat(animal)) { animal.meow(); // TypeScript knows animal is Cat } else { animal.bark(); // TypeScript knows animal is Dog } }

Filtering Null/Undefined Values

function isDefined<T>(value: T | undefined | null): value is T { return value !== undefined && value !== null; } const values = [1, undefined, 2, null, 3]; const definedValues = values.filter(isDefined); // number[]