0
# Combinators
1
2
Functions for composing complex types from simpler ones, enabling validation of objects, arrays, unions, intersections, and more.
3
4
## Capabilities
5
6
### Object Type Combinator
7
8
Creates a codec for objects with required properties.
9
10
```typescript { .api }
11
/**
12
* Create an interface codec from properties
13
* @param props - Object mapping property names to their codecs
14
* @param name - Optional name for the codec
15
*/
16
function type<P extends Props>(props: P, name?: string): TypeC<P>;
17
18
interface TypeC<P extends Props> extends InterfaceType<P,
19
{ [K in keyof P]: TypeOf<P[K]> },
20
{ [K in keyof P]: OutputOf<P[K]> },
21
unknown
22
> {}
23
```
24
25
**Usage Example:**
26
27
```typescript
28
import * as t from "io-ts";
29
30
const User = t.type({
31
name: t.string,
32
age: t.number,
33
email: t.string
34
});
35
36
type User = t.TypeOf<typeof User>;
37
// User = { name: string; age: number; email: string }
38
39
const result = User.decode({
40
name: "Alice",
41
age: 30,
42
email: "alice@example.com"
43
});
44
// result: Right({ name: "Alice", age: 30, email: "alice@example.com" })
45
```
46
47
### Partial Type Combinator
48
49
Creates a codec for objects with optional properties.
50
51
```typescript { .api }
52
/**
53
* Create a partial interface codec where all properties are optional
54
* @param props - Object mapping property names to their codecs
55
* @param name - Optional name for the codec
56
*/
57
function partial<P extends Props>(props: P, name?: string): PartialC<P>;
58
59
interface PartialC<P extends Props> extends PartialType<P,
60
{ [K in keyof P]?: TypeOf<P[K]> },
61
{ [K in keyof P]?: OutputOf<P[K]> },
62
unknown
63
> {}
64
```
65
66
**Usage Example:**
67
68
```typescript
69
import * as t from "io-ts";
70
71
const PartialUser = t.partial({
72
name: t.string,
73
age: t.number,
74
email: t.string
75
});
76
77
type PartialUser = t.TypeOf<typeof PartialUser>;
78
// PartialUser = { name?: string; age?: number; email?: string }
79
80
const result = PartialUser.decode({ name: "Alice" });
81
// result: Right({ name: "Alice" })
82
```
83
84
### Array Combinator
85
86
Creates a codec for arrays with elements of a specific type.
87
88
```typescript { .api }
89
/**
90
* Create an array codec with elements of type C
91
* @param item - Codec for array elements
92
* @param name - Optional name for the codec
93
*/
94
function array<C extends Mixed>(item: C, name?: string): ArrayC<C>;
95
96
interface ArrayC<C extends Mixed> extends ArrayType<C,
97
Array<TypeOf<C>>,
98
Array<OutputOf<C>>,
99
unknown
100
> {}
101
```
102
103
**Usage Example:**
104
105
```typescript
106
import * as t from "io-ts";
107
108
const NumberArray = t.array(t.number);
109
const StringArray = t.array(t.string);
110
111
const numbers = NumberArray.decode([1, 2, 3]);
112
// result: Right([1, 2, 3])
113
114
const mixed = NumberArray.decode([1, "2", 3]);
115
// result: Left([validation error for index 1])
116
```
117
118
### Union Combinator
119
120
Creates a codec that validates against one of several possible types.
121
122
```typescript { .api }
123
/**
124
* Create a union codec that validates against one of the provided codecs
125
* @param codecs - Array of at least 2 codecs to try
126
* @param name - Optional name for the codec
127
*/
128
function union<CS extends [Mixed, Mixed, ...Array<Mixed>]>(
129
codecs: CS,
130
name?: string
131
): UnionC<CS>;
132
133
interface UnionC<CS extends [Mixed, Mixed, ...Array<Mixed>]> extends UnionType<CS,
134
TypeOf<CS[number]>,
135
OutputOf<CS[number]>,
136
unknown
137
> {}
138
```
139
140
**Usage Example:**
141
142
```typescript
143
import * as t from "io-ts";
144
145
const StringOrNumber = t.union([t.string, t.number]);
146
const Status = t.union([
147
t.literal('pending'),
148
t.literal('completed'),
149
t.literal('failed')
150
]);
151
152
const result1 = StringOrNumber.decode("hello");
153
// result: Right("hello")
154
155
const result2 = StringOrNumber.decode(42);
156
// result: Right(42)
157
158
const status = Status.decode('pending');
159
// result: Right('pending')
160
```
161
162
### Intersection Combinator
163
164
Creates a codec that validates against all of the provided types.
165
166
```typescript { .api }
167
/**
168
* Create an intersection codec that validates against all provided codecs
169
* @param codecs - Array of at least 2 codecs to intersect
170
* @param name - Optional name for the codec
171
*/
172
function intersection<CS extends [Mixed, Mixed, ...Array<Mixed>]>(
173
codecs: CS,
174
name?: string
175
): IntersectionC<CS>;
176
177
interface IntersectionC<CS extends [Mixed, Mixed, ...Array<Mixed>]> extends IntersectionType<CS,
178
// Complex conditional type for intersection result
179
unknown,
180
unknown,
181
unknown
182
> {}
183
```
184
185
**Usage Example:**
186
187
```typescript
188
import * as t from "io-ts";
189
190
const Named = t.type({ name: t.string });
191
const Aged = t.type({ age: t.number });
192
const Person = t.intersection([Named, Aged]);
193
194
type Person = t.TypeOf<typeof Person>;
195
// Person = { name: string } & { age: number }
196
197
const result = Person.decode({ name: "Alice", age: 30 });
198
// result: Right({ name: "Alice", age: 30 })
199
```
200
201
### Tuple Combinator
202
203
Creates a codec for fixed-length arrays with specific types at each position.
204
205
```typescript { .api }
206
/**
207
* Create a tuple codec with specific types at each position
208
* @param codecs - Array of codecs for each tuple position
209
* @param name - Optional name for the codec
210
*/
211
function tuple<CS extends [Mixed, ...Array<Mixed>]>(
212
codecs: CS,
213
name?: string
214
): TupleC<CS>;
215
216
interface TupleC<CS extends [Mixed, ...Array<Mixed>]> extends TupleType<CS,
217
// Complex conditional type for tuple result
218
unknown,
219
unknown,
220
unknown
221
> {}
222
```
223
224
**Usage Example:**
225
226
```typescript
227
import * as t from "io-ts";
228
229
const Coordinate = t.tuple([t.number, t.number]);
230
const NamedPoint = t.tuple([t.string, t.number, t.number]);
231
232
const point = Coordinate.decode([10, 20]);
233
// result: Right([10, 20])
234
235
const namedPoint = NamedPoint.decode(["origin", 0, 0]);
236
// result: Right(["origin", 0, 0])
237
```
238
239
### Record Combinator
240
241
Creates a codec for objects with dynamic keys and uniform value types.
242
243
```typescript { .api }
244
/**
245
* Create a record codec with domain keys and codomain values
246
* @param domain - Codec for the keys
247
* @param codomain - Codec for the values
248
* @param name - Optional name for the codec
249
*/
250
function record<D extends Mixed, C extends Mixed>(
251
domain: D,
252
codomain: C,
253
name?: string
254
): RecordC<D, C>;
255
256
interface RecordC<D extends Mixed, C extends Mixed> extends DictionaryType<D, C,
257
{ [K in TypeOf<D>]: TypeOf<C> },
258
{ [K in OutputOf<D>]: OutputOf<C> },
259
unknown
260
> {}
261
```
262
263
**Usage Example:**
264
265
```typescript
266
import * as t from "io-ts";
267
268
const StringRecord = t.record(t.string, t.number);
269
const Scores = t.record(t.keyof({ alice: null, bob: null, charlie: null }), t.number);
270
271
const scores = StringRecord.decode({ math: 95, science: 87 });
272
// result: Right({ math: 95, science: 87 })
273
274
const playerScores = Scores.decode({ alice: 100, bob: 85 });
275
// result: Right({ alice: 100, bob: 85 })
276
```
277
278
### Literal Combinator
279
280
Creates a codec for exact literal values.
281
282
```typescript { .api }
283
/**
284
* Create a literal value codec
285
* @param value - The exact value to match
286
* @param name - Optional name for the codec
287
*/
288
function literal<V extends LiteralValue>(value: V, name?: string): LiteralC<V>;
289
290
interface LiteralC<V extends LiteralValue> extends LiteralType<V> {}
291
292
type LiteralValue = string | number | boolean;
293
```
294
295
**Usage Example:**
296
297
```typescript
298
import * as t from "io-ts";
299
300
const StatusPending = t.literal('pending');
301
const Version = t.literal(1);
302
const IsActive = t.literal(true);
303
304
const result = StatusPending.decode('pending');
305
// result: Right('pending')
306
307
const invalid = StatusPending.decode('completed');
308
// result: Left([validation error])
309
```
310
311
### KeyOf Combinator
312
313
Creates a codec for keys of an object type.
314
315
```typescript { .api }
316
/**
317
* Create a keyof codec from object keys
318
* @param keys - Object whose keys define the valid values
319
* @param name - Optional name for the codec
320
*/
321
function keyof<D extends { [key: string]: unknown }>(
322
keys: D,
323
name?: string
324
): KeyofC<D>;
325
326
interface KeyofC<D extends { [key: string]: unknown }> extends KeyofType<D> {}
327
```
328
329
**Usage Example:**
330
331
```typescript
332
import * as t from "io-ts";
333
334
const Color = t.keyof({ red: null, green: null, blue: null });
335
336
type Color = t.TypeOf<typeof Color>;
337
// Color = "red" | "green" | "blue"
338
339
const result = Color.decode('red');
340
// result: Right('red')
341
342
const invalid = Color.decode('yellow');
343
// result: Left([validation error])
344
```
345
346
### Readonly Combinators
347
348
Make codecs readonly to prevent mutations.
349
350
```typescript { .api }
351
/**
352
* Make a codec readonly
353
* @param codec - The codec to make readonly
354
* @param name - Optional name for the codec
355
*/
356
function readonly<C extends Mixed>(codec: C, name?: string): ReadonlyC<C>;
357
358
interface ReadonlyC<C extends Mixed> extends ReadonlyType<C,
359
Readonly<TypeOf<C>>,
360
Readonly<OutputOf<C>>,
361
unknown
362
> {}
363
364
/**
365
* Create a readonly array codec
366
* @param item - Codec for array elements
367
* @param name - Optional name for the codec
368
*/
369
function readonlyArray<C extends Mixed>(item: C, name?: string): ReadonlyArrayC<C>;
370
371
interface ReadonlyArrayC<C extends Mixed> extends ReadonlyArrayType<C,
372
ReadonlyArray<TypeOf<C>>,
373
ReadonlyArray<OutputOf<C>>,
374
unknown
375
> {}
376
```
377
378
**Usage Example:**
379
380
```typescript
381
import * as t from "io-ts";
382
383
const ReadonlyUser = t.readonly(t.type({
384
name: t.string,
385
age: t.number
386
}));
387
388
const ReadonlyNumbers = t.readonlyArray(t.number);
389
390
type ReadonlyUser = t.TypeOf<typeof ReadonlyUser>;
391
// ReadonlyUser = Readonly<{ name: string; age: number }>
392
```
393
394
### Exact and Strict Combinators
395
396
Strip additional properties from objects.
397
398
```typescript { .api }
399
/**
400
* Strip additional properties from a codec
401
* @param codec - The codec to make exact
402
* @param name - Optional name for the codec
403
*/
404
function exact<C extends HasProps>(codec: C, name?: string): ExactC<C>;
405
406
interface ExactC<C extends HasProps> extends ExactType<C, TypeOf<C>, OutputOf<C>, InputOf<C>> {}
407
408
/**
409
* Create a strict interface codec (strips extra properties)
410
* @param props - Object mapping property names to their codecs
411
* @param name - Optional name for the codec
412
*/
413
function strict<P extends Props>(props: P, name?: string): ExactC<TypeC<P>>;
414
```
415
416
**Usage Example:**
417
418
```typescript
419
import * as t from "io-ts";
420
421
const User = t.type({
422
name: t.string,
423
age: t.number
424
});
425
426
const StrictUser = t.strict({
427
name: t.string,
428
age: t.number
429
});
430
431
const data = { name: "Alice", age: 30, extra: "ignored" };
432
433
const lenient = User.decode(data);
434
// result: Right({ name: "Alice", age: 30, extra: "ignored" })
435
436
const strict = StrictUser.decode(data);
437
// result: Right({ name: "Alice", age: 30 }) - extra property stripped
438
```
439
440
### Recursive Combinator
441
442
Create recursive type definitions for self-referential data structures.
443
444
```typescript { .api }
445
/**
446
* Create a recursive codec for self-referential types
447
* @param name - Name for the recursive type
448
* @param definition - Function that defines the recursive structure
449
*/
450
function recursion<A, O = A, I = unknown, C extends Type<A, O, I> = Type<A, O, I>>(
451
name: string,
452
definition: (self: C) => C
453
): RecursiveType<C, A, O, I>;
454
```
455
456
**Usage Example:**
457
458
```typescript
459
import * as t from "io-ts";
460
461
interface Category {
462
name: string;
463
subcategories: Category[];
464
}
465
466
const Category: t.Type<Category> = t.recursion('Category', Self =>
467
t.type({
468
name: t.string,
469
subcategories: t.array(Self)
470
})
471
);
472
473
const data = {
474
name: "Electronics",
475
subcategories: [
476
{
477
name: "Computers",
478
subcategories: []
479
},
480
{
481
name: "Phones",
482
subcategories: []
483
}
484
]
485
};
486
487
const result = Category.decode(data);
488
// result: Right(nested category structure)
489
```
490
491
## Advanced Usage Patterns
492
493
### Complex Object Validation
494
495
```typescript
496
import * as t from "io-ts";
497
498
const User = t.type({
499
id: t.number,
500
profile: t.type({
501
name: t.string,
502
email: t.string,
503
preferences: t.partial({
504
theme: t.union([t.literal('light'), t.literal('dark')]),
505
notifications: t.boolean
506
})
507
}),
508
roles: t.array(t.string),
509
metadata: t.record(t.string, t.unknown)
510
});
511
512
type User = t.TypeOf<typeof User>;
513
```
514
515
### Discriminated Unions
516
517
```typescript
518
import * as t from "io-ts";
519
520
const Shape = t.union([
521
t.type({
522
kind: t.literal('circle'),
523
radius: t.number
524
}),
525
t.type({
526
kind: t.literal('rectangle'),
527
width: t.number,
528
height: t.number
529
}),
530
t.type({
531
kind: t.literal('triangle'),
532
base: t.number,
533
height: t.number
534
})
535
]);
536
537
type Shape = t.TypeOf<typeof Shape>;
538
// Shape = { kind: 'circle'; radius: number }
539
// | { kind: 'rectangle'; width: number; height: number }
540
// | { kind: 'triangle'; base: number; height: number }
541
```