TypeScript Assertion Functions: Complete Guide to Runtime Checks

TypeScript assertion functions provide a powerful way to validate data at runtime while maintaining type safety. In this comprehensive guide, we’ll explore how to leverage assertion functions to write more robust and type-safe code.

Assertion functions are special functions that perform runtime checks and tell TypeScript that certain conditions are true. They help bridge the gap between compile-time type checking and runtime validation, making your applications more reliable.

Table of Contents

Understanding Assertion Functions

An assertion function is a function that throws an error if a condition is false. TypeScript recognizes these functions and uses them to narrow types in the code that follows them. Here’s a basic example:

function assertIsString(val: any): asserts val is string {
  if (typeof val !== 'string') {
    throw new Error('Value must be a string');
  }
}
Code language: JavaScript (javascript)

Basic Syntax and Usage

The syntax for assertion functions follows this pattern:

function assertCondition(value: any): asserts value is Type {
  // Runtime check logic
}
Code language: JavaScript (javascript)

Let’s see how to use our string assertion function:

let value: any = 'Hello World';

// TypeScript doesn't know the type here
console.log(value.length); // Error: Object is of type 'any'

assertIsString(value);

// TypeScript now knows value is a string
console.log(value.length); // Works fine
Code language: JavaScript (javascript)

Creating Custom Assertion Functions

You can create assertion functions for complex types and custom type guards:

interface User {
  id: number;
  name: string;
}

function assertIsUser(value: any): asserts value is User {
  if (!value || typeof value !== 'object') {
    throw new Error('Value must be an object');
  }
  
  if (!('id' in value) || typeof value.id !== 'number') {
    throw new Error('Value must have a numeric id');
  }
  
  if (!('name' in value) || typeof value.name !== 'string') {
    throw new Error('Value must have a string name');
  }
}
Code language: JavaScript (javascript)

Assertion Functions with Predicates

You can also create assertion functions using predicates:

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function assertString(value: unknown): asserts value is string {
  if (!isString(value)) {
    throw new Error('Value must be a string');
  }
}
Code language: JavaScript (javascript)

Practical Applications

API Response Validation

interface ApiResponse {
  status: number;
  data: unknown;
}

function assertApiResponse(value: unknown): asserts value is ApiResponse {
  if (!value || typeof value !== 'object') {
    throw new Error('Invalid API response');
  }
  
  if (!('status' in value) || typeof value.status !== 'number') {
    throw new Error('API response must have a numeric status');
  }
  
  if (!('data' in value)) {
    throw new Error('API response must have data');
  }
}

async function fetchData() {
  const response = await fetch('/api/data');
  const json = await response.json();
  
  assertApiResponse(json);
  // TypeScript now knows json is ApiResponse
  console.log(json.status);
}
Code language: JavaScript (javascript)

Form Data Validation

interface FormData {
  email: string;
  password: string;
}

function assertFormData(value: unknown): asserts value is FormData {
  if (!value || typeof value !== 'object') {
    throw new Error('Invalid form data');
  }
  
  if (!('email' in value) || typeof value.email !== 'string') {
    throw new Error('Form must have a valid email');
  }
  
  if (!('password' in value) || typeof value.password !== 'string') {
    throw new Error('Form must have a valid password');
  }
}
Code language: JavaScript (javascript)

Best Practices

1. Descriptive Error Messages

Always provide clear, descriptive error messages:

function assertPositiveNumber(value: unknown): asserts value is number {
  if (typeof value !== 'number' || value <= 0) {
    throw new Error(`Expected positive number, got ${typeof value}: ${value}`);
  }
}
Code language: JavaScript (javascript)

2. Composition

Compose multiple assertions for complex validations:

function assertNonEmptyString(value: unknown): asserts value is string {
  assertString(value);
  if (value.length === 0) {
    throw new Error('String must not be empty');
  }
}
Code language: JavaScript (javascript)

3. Type Narrowing

Leverage type narrowing for more precise type checking:

function assertArray<T>(value: unknown, itemGuard: (item: unknown) => item is T): asserts value is T[] {
  if (!Array.isArray(value)) {
    throw new Error('Value must be an array');
  }
  
  value.forEach((item, index) => {
    if (!itemGuard(item)) {
      throw new Error(`Invalid array item at index ${index}`);
    }
  });
}
Code language: HTML, XML (xml)

Common Pitfalls and Solutions

1. Assertion Function Scope

Be careful with assertion functions in callbacks:

// Don't do this
array.forEach((item) => {
  assertString(item); // Type information is lost after the callback
});

// Do this instead
for (const item of array) {
  assertString(item); // Type information is preserved
  console.log(item.toUpperCase());
}
Code language: PHP (php)

2. Assertion vs Type Guard

Choose the right tool for the job:

// Type guard - returns boolean
function isNumber(value: unknown): value is number {
  return typeof value === 'number';
}

// Assertion function - throws error
function assertNumber(value: unknown): asserts value is number {
  if (typeof value !== 'number') {
    throw new Error('Value must be a number');
  }
}
Code language: JavaScript (javascript)

Conclusion

TypeScript assertion functions are a powerful tool for runtime type checking while maintaining type safety. They bridge the gap between compile-time and runtime type checking, making your applications more reliable and maintainable.

By following best practices and understanding common pitfalls, you can effectively use assertion functions to improve your TypeScript code’s quality and type safety. Remember to provide clear error messages and compose assertions for complex validations.

Try implementing assertion functions in your next TypeScript project to experience the benefits of enhanced type safety and runtime validation working together seamlessly.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share via
Copy link
Powered by Social Snap