0
# Composite Types
1
2
Complex data structure validators for arrays, objects, tuples, and records. These types allow validation of structured data with specific element and property types.
3
4
## Capabilities
5
6
### Array
7
8
Validates arrays where all elements conform to a specific element type.
9
10
```typescript { .api }
11
/**
12
* Creates a validator for arrays of a specific element type
13
* @param element - Runtype for validating each array element
14
* @example Array(String).check(["a", "b", "c"]) // ["a", "b", "c"]
15
* @example Array(Number).check([1, 2, "3"]) // throws ValidationError
16
*/
17
function Array<R extends Runtype.Core>(element: R): Array<R>;
18
19
interface Array<R extends Runtype.Core = Runtype.Core> extends Runtype<Static<R>[], Parsed<R>[]> {
20
tag: "array";
21
element: R;
22
asReadonly(): Array.Readonly<R>;
23
}
24
```
25
26
**Usage Examples:**
27
28
```typescript
29
import { Array, String, Number, Object } from "runtypes";
30
31
// Basic arrays
32
const StringArray = Array(String);
33
const NumberArray = Array(Number);
34
35
const names = StringArray.check(["Alice", "Bob", "Charlie"]);
36
const scores = NumberArray.check([95, 87, 92]);
37
38
// Nested arrays
39
const Matrix = Array(Array(Number));
40
const grid = Matrix.check([[1, 2], [3, 4], [5, 6]]);
41
42
// Arrays of objects
43
const UserArray = Array(Object({
44
id: Number,
45
name: String,
46
active: Boolean
47
}));
48
49
const users = UserArray.check([
50
{ id: 1, name: "Alice", active: true },
51
{ id: 2, name: "Bob", active: false }
52
]);
53
54
// Readonly arrays
55
const ReadonlyNames = Array(String).asReadonly();
56
type ReadonlyNamesType = Static<typeof ReadonlyNames>; // readonly string[]
57
```
58
59
### Object
60
61
Validates objects with specific property types and structures.
62
63
```typescript { .api }
64
/**
65
* Creates a validator for objects with specified property types
66
* @param fields - Object mapping property names to their runtypes
67
* @example Object({ name: String, age: Number }).check({ name: "Alice", age: 25 })
68
*/
69
function Object<O extends Object.Fields>(fields: O): Object<O>;
70
71
interface Object<O extends Object.Fields = Object.Fields> extends Runtype<ObjectStatic<O>, ObjectParsed<O>> {
72
tag: "object";
73
fields: O;
74
isExact: boolean;
75
76
asPartial(): Object<PartialFields<O>>;
77
asReadonly(): Object.Readonly<O>;
78
pick<K extends keyof O>(...keys: K[]): Object<Pick<O, K>>;
79
omit<K extends keyof O>(...keys: K[]): Object<Omit<O, K>>;
80
extend<P extends Object.Fields>(fields: P): Object<O & P>;
81
exact(): Object<O>;
82
}
83
84
namespace Object {
85
export type Fields = Record<PropertyKey, Runtype.Core | Optional>;
86
}
87
```
88
89
**Usage Examples:**
90
91
```typescript
92
import { Object, String, Number, Boolean, Optional } from "runtypes";
93
94
// Basic object validation
95
const User = Object({
96
id: Number,
97
name: String,
98
email: String,
99
active: Boolean
100
});
101
102
const user = User.check({
103
id: 1,
104
name: "Alice",
105
email: "alice@example.com",
106
active: true
107
});
108
109
// Optional properties
110
const UserWithOptionals = Object({
111
id: Number,
112
name: String,
113
email: String.optional(),
114
age: Number.optional(),
115
bio: String.default("No bio provided")
116
});
117
118
// Object manipulation methods
119
const PartialUser = User.asPartial(); // All properties optional
120
const UserNameOnly = User.pick("name", "email"); // Only name and email
121
const UserWithoutId = User.omit("id"); // All except id
122
123
// Extended objects
124
const AdminUser = User.extend({
125
role: String,
126
permissions: Array(String)
127
});
128
129
// Exact objects (reject extra properties)
130
const StrictUser = User.exact();
131
StrictUser.check({
132
id: 1,
133
name: "Alice",
134
email: "alice@example.com",
135
active: true,
136
extra: "not allowed" // throws ValidationError
137
});
138
```
139
140
### Tuple
141
142
Validates fixed-length arrays with specific types for each position.
143
144
```typescript { .api }
145
/**
146
* Creates a validator for tuples with specific element types at each position
147
* @param components - Runtypes for each tuple element in order
148
* @example Tuple(String, Number).check(["Alice", 25]) // ["Alice", 25]
149
* @example Tuple(String, Number).check(["Alice"]) // throws ValidationError (wrong length)
150
*/
151
function Tuple<T extends readonly Runtype[]>(...components: T): TupleRuntype<T>;
152
153
interface TupleRuntype<T> extends Runtype<TupleType<T>> {
154
tag: "tuple";
155
components: T;
156
asReadonly(): ReadonlyTupleRuntype<T>;
157
}
158
```
159
160
**Usage Examples:**
161
162
```typescript
163
import { Tuple, String, Number, Boolean } from "runtypes";
164
165
// Basic tuples
166
const NameAge = Tuple(String, Number);
167
const person = NameAge.check(["Alice", 25]); // ["Alice", 25]
168
169
// Coordinates
170
const Point2D = Tuple(Number, Number);
171
const Point3D = Tuple(Number, Number, Number);
172
173
const point2d = Point2D.check([10, 20]);
174
const point3d = Point3D.check([10, 20, 30]);
175
176
// Mixed types
177
const UserRecord = Tuple(Number, String, Boolean);
178
const record = UserRecord.check([1, "Alice", true]);
179
180
// Rest elements (using Spread)
181
import { Spread } from "runtypes";
182
183
const NumbersWithLabel = Tuple(String, Spread(Array(Number)));
184
const data = NumbersWithLabel.check(["scores", 95, 87, 92, 88]);
185
186
// Readonly tuples
187
const ReadonlyPoint = Point2D.asReadonly();
188
type ReadonlyPointType = Static<typeof ReadonlyPoint>; // readonly [number, number]
189
```
190
191
### Record
192
193
Validates objects with dynamic keys but uniform value types.
194
195
```typescript { .api }
196
/**
197
* Creates a validator for objects with dynamic keys and uniform value types
198
* @param key - Runtype for validating object keys
199
* @param value - Runtype for validating all object values
200
* @example Record(String, Number).check({ a: 1, b: 2 }) // { a: 1, b: 2 }
201
*/
202
function Record<K extends PropertyKey, V>(
203
key: Runtype<K>,
204
value: Runtype<V>
205
): RecordRuntype<K, V>;
206
207
interface RecordRuntype<K, V> extends Runtype<Record<K, V>> {
208
tag: "record";
209
key: Runtype<K>;
210
value: Runtype<V>;
211
}
212
```
213
214
**Usage Examples:**
215
216
```typescript
217
import { Record, String, Number, Union, Literal } from "runtypes";
218
219
// String keys with number values
220
const StringNumberRecord = Record(String, Number);
221
const scores = StringNumberRecord.check({
222
alice: 95,
223
bob: 87,
224
charlie: 92
225
});
226
227
// Specific string keys
228
const AllowedKeys = Union(Literal("red"), Literal("green"), Literal("blue"));
229
const ColorValues = Record(AllowedKeys, Number);
230
const colors = ColorValues.check({
231
red: 255,
232
green: 128,
233
blue: 64
234
});
235
236
// Number keys (for array-like objects)
237
const NumberStringRecord = Record(Number, String);
238
const arrayLike = NumberStringRecord.check({
239
0: "first",
240
1: "second",
241
2: "third"
242
});
243
244
// Complex value types
245
const UserProfiles = Record(String, Object({
246
name: String,
247
age: Number,
248
active: Boolean
249
}));
250
251
const profiles = UserProfiles.check({
252
"user1": { name: "Alice", age: 25, active: true },
253
"user2": { name: "Bob", age: 30, active: false }
254
});
255
```
256
257
## Advanced Patterns
258
259
### Nested Structures
260
261
```typescript
262
import { Object, Array, Tuple, Record, String, Number } from "runtypes";
263
264
// Deeply nested object
265
const Company = Object({
266
name: String,
267
departments: Array(Object({
268
name: String,
269
employees: Array(Object({
270
id: Number,
271
name: String,
272
position: String,
273
salary: Number
274
})),
275
budget: Number
276
})),
277
metadata: Record(String, String)
278
});
279
280
// Mixed structure with tuples and arrays
281
const DataSet = Object({
282
name: String,
283
points: Array(Tuple(Number, Number)), // Array of [x, y] coordinates
284
labels: Record(Number, String), // Index to label mapping
285
config: Object({
286
title: String,
287
axes: Tuple(String, String) // [x-axis label, y-axis label]
288
})
289
});
290
```
291
292
### Optional and Partial Patterns
293
294
```typescript
295
import { Object, String, Number, Optional } from "runtypes";
296
297
// Explicit optional properties
298
const UserProfile = Object({
299
id: Number,
300
name: String,
301
email: String,
302
phone: String.optional(),
303
avatar: String.optional(),
304
bio: String.default("No bio available")
305
});
306
307
// Convert existing object to partial
308
const PartialProfile = UserProfile.asPartial();
309
// All properties become optional
310
311
// Update patterns
312
const UserUpdate = UserProfile.omit("id").asPartial();
313
// Remove id, make rest optional for updates
314
```
315
316
### Type Extraction
317
318
```typescript
319
import { Object, Array, String, Number, type Static } from "runtypes";
320
321
const ApiResponse = Object({
322
data: Array(Object({
323
id: Number,
324
title: String,
325
tags: Array(String)
326
})),
327
meta: Object({
328
total: Number,
329
page: Number,
330
hasMore: Boolean
331
})
332
});
333
334
// Extract TypeScript types
335
type ApiResponseType = Static<typeof ApiResponse>;
336
// {
337
// data: {
338
// id: number;
339
// title: string;
340
// tags: string[];
341
// }[];
342
// meta: {
343
// total: number;
344
// page: number;
345
// hasMore: boolean;
346
// };
347
// }
348
349
// Use in functions
350
function processApiResponse(response: unknown): ApiResponseType {
351
return ApiResponse.check(response);
352
}
353
```