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:
- Is an object
- Contains all properties the pattern defines
- 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
- Match on discriminant properties first: For discriminated unions, check the discriminant
- Use partial patterns: Only specify properties you need to check
- Leverage type narrowing: Let TypeScript infer precise types
- 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