Skip to Content
Objects

Last Updated: 3/6/2026


Object Patterns

Object patterns match objects that contain specific properties with values matching sub-patterns.

Basic Object Matching

An object pattern matches if the input:

  1. Is an object
  2. Contains all properties the pattern defines
  3. Each property matches the corresponding sub-pattern
import { match, P } from 'ts-pattern'; type Input = | { type: 'user'; name: string } | { type: 'image'; src: string } | { type: 'video'; seconds: number }; let input: Input = { type: 'user', name: 'Gabriel' }; const output = match(input) .with({ type: 'image' }, (img) => `image: ${img.src}`) .with({ type: 'video', seconds: 10 }, () => 'video of 10 seconds') .with({ type: 'user' }, ({ name }) => `user: ${name}`) .exhaustive(); console.log(output); // => 'user: Gabriel'

Nested Objects

Patterns can match deeply nested structures:

type User = { id: number; profile: { name: string; address: { city: string; country: string; }; }; }; const user: User = { id: 1, profile: { name: 'Alice', address: { city: 'Paris', country: 'France', }, }, }; match(user) .with( { profile: { address: { country: 'France', }, }, }, (u) => `French user: ${u.profile.name}` ) .otherwise(() => 'Non-French user');

Partial Matching

You don’t need to specify all properties - only the ones you care about:

type Article = { id: number; title: string; content: string; author: string; published: boolean; }; match(article) // Only checks the 'published' property .with({ published: true }, (a) => `Published: ${a.title}`) .with({ published: false }, (a) => `Draft: ${a.title}`) .exhaustive();

Using Wildcards in Objects

type Response = | { status: 'success'; data: string } | { status: 'success'; data: number } | { status: 'error'; error: string }; match(response) .with({ status: 'success', data: P.string }, (r) => `String data: ${r.data}` ) .with({ status: 'success', data: P.number }, (r) => `Number data: ${r.data}` ) .with({ status: 'error' }, (r) => `Error: ${r.error}`) .exhaustive();

Optional Properties with P.optional

type User = { name: string; email?: string; age?: number; }; match(user) .with( { name: P.string, email: P.optional(P.string) }, (u) => { // u.email: string | undefined return u.email ? `Email: ${u.email}` : 'No email'; } ) .otherwise(() => 'Invalid user');

Selecting Object Properties

Use P.select() to extract specific properties:

type Post = { id: number; author: { name: string; id: number }; content: string; }; match(post) .with( { author: { name: P.select('authorName') }, content: P.select('text') }, ({ authorName, text }) => { // authorName: string, text: string return `${authorName} wrote: ${text}`; } ) .otherwise(() => '');

Discriminated Unions

Object patterns work great with discriminated unions:

type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; sideLength: number } | { kind: 'rectangle'; width: number; height: number }; const getArea = (shape: Shape) => match(shape) .with({ kind: 'circle' }, (s) => Math.PI * s.radius ** 2) .with({ kind: 'square' }, (s) => s.sideLength ** 2) .with({ kind: 'rectangle' }, (s) => s.width * s.height) .exhaustive();

Complex Patterns

Combine multiple pattern features:

type Event = | { type: 'click'; x: number; y: number; button: 'left' | 'right' } | { type: 'keypress'; key: string; ctrl: boolean } | { type: 'scroll'; deltaY: number }; match(event) .with( { type: 'click', button: 'right', x: P.number.gt(100), }, (e) => `Right click at (${e.x}, ${e.y})` ) .with( { type: 'keypress', ctrl: true, key: P.union('s', 'S'), }, () => 'Save command' ) .with( { type: 'scroll', deltaY: P.when((d) => Math.abs(d) > 50) }, (e) => `Big scroll: ${e.deltaY}` ) .otherwise(() => 'Other event');

Best Practices

  1. Match on discriminant properties first: For discriminated unions, check the discriminant
  2. Use partial patterns: Only specify properties you need to check
  3. Leverage type narrowing: Let TypeScript infer precise types
  4. Combine with wildcards: Mix specific values and wildcards as needed
// Good: Clear discriminant matching match(response) .with({ status: 'success' }, (r) => handleSuccess(r.data)) .with({ status: 'error' }, (r) => handleError(r.error)) .exhaustive(); // Less clear: Too specific match(response) .with({ status: 'success', data: P.string, timestamp: P.number }, ...) // Checking properties you don't need