0
# Utility Functions and Codec Modifiers
1
2
Utility functions for creating, modifying, and composing codecs including fallback values, custom validation, and lens generation. These utilities enable advanced codec composition and customization patterns.
3
4
## Capabilities
5
6
### Fallback Values
7
8
Creates codecs that use a fallback value when the original codec validation fails, providing graceful degradation.
9
10
```typescript { .api }
11
/**
12
* Creates a codec that uses a fallback value when validation fails
13
* @param codec - The original codec to try first
14
* @param a - The fallback value to use on validation failure
15
* @param name - Optional name for the codec
16
* @returns Codec that never fails, using fallback on validation errors
17
*/
18
function withFallback<C extends t.Any>(
19
codec: C,
20
a: t.TypeOf<C>,
21
name?: string
22
): C;
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
import { withFallback } from "io-ts-types";
29
import * as t from "io-ts";
30
31
// Number codec with fallback to 0
32
const NumberWithFallback = withFallback(t.number, 0);
33
34
const result1 = NumberWithFallback.decode(42);
35
// Right(42)
36
37
const result2 = NumberWithFallback.decode("invalid");
38
// Right(0) - fallback value used instead of validation error
39
40
const result3 = NumberWithFallback.decode(null);
41
// Right(0) - fallback value used
42
43
// String codec with default value
44
const NameWithDefault = withFallback(t.string, "Anonymous");
45
46
const result4 = NameWithDefault.decode("Alice");
47
// Right("Alice")
48
49
const result5 = NameWithDefault.decode(123);
50
// Right("Anonymous") - fallback used for invalid input
51
```
52
53
### Custom Error Messages
54
55
Creates codecs with custom error messages using a message function, providing more meaningful validation feedback.
56
57
```typescript { .api }
58
/**
59
* Creates a codec with custom error messages
60
* @param codec - The original codec
61
* @param message - Function that generates custom error messages
62
* @returns Codec with custom error reporting
63
*/
64
function withMessage<C extends t.Any>(
65
codec: C,
66
message: (i: t.InputOf<C>, c: t.Context) => string
67
): C;
68
```
69
70
**Usage Examples:**
71
72
```typescript
73
import { withMessage } from "io-ts-types";
74
import * as t from "io-ts";
75
76
// Custom error message for number validation
77
const PositiveNumber = withMessage(
78
t.refinement(t.number, (n) => n > 0, "PositiveNumber"),
79
(input, context) => `Expected a positive number, got: ${JSON.stringify(input)}`
80
);
81
82
const result1 = PositiveNumber.decode(5);
83
// Right(5)
84
85
const result2 = PositiveNumber.decode(-1);
86
// Left([{ message: "Expected a positive number, got: -1", ... }])
87
88
const result3 = PositiveNumber.decode("not-a-number");
89
// Left([{ message: "Expected a positive number, got: \"not-a-number\"", ... }])
90
91
// Custom message for email-like validation
92
const EmailLike = withMessage(
93
t.refinement(t.string, (s) => s.includes("@"), "EmailLike"),
94
(input) => `"${input}" doesn't look like an email address`
95
);
96
97
const result4 = EmailLike.decode("user@example.com");
98
// Right("user@example.com")
99
100
const result5 = EmailLike.decode("invalid-email");
101
// Left([{ message: "\"invalid-email\" doesn't look like an email address", ... }])
102
```
103
104
### Custom Validation
105
106
Creates codecs with custom validate functions while preserving other codec properties.
107
108
```typescript { .api }
109
/**
110
* Creates a codec with a custom validate function
111
* @param codec - The original codec to modify
112
* @param validate - Custom validation function
113
* @param name - Optional name for the codec
114
* @returns Codec with custom validation logic
115
*/
116
function withValidate<C extends t.Any>(
117
codec: C,
118
validate: C['validate'],
119
name?: string
120
): C;
121
```
122
123
**Usage Examples:**
124
125
```typescript
126
import { withValidate } from "io-ts-types";
127
import * as t from "io-ts";
128
import * as E from "fp-ts/lib/Either";
129
130
// Custom validation that normalizes input
131
const TrimmedString = withValidate(
132
t.string,
133
(input, context) => {
134
if (typeof input !== "string") {
135
return t.failure(input, context);
136
}
137
const trimmed = input.trim();
138
return trimmed.length > 0
139
? t.success(trimmed)
140
: t.failure(input, context, "String cannot be empty after trimming");
141
},
142
"TrimmedString"
143
);
144
145
const result1 = TrimmedString.decode(" hello ");
146
// Right("hello") - trimmed
147
148
const result2 = TrimmedString.decode(" ");
149
// Left([ValidationError]) - empty after trimming
150
151
const result3 = TrimmedString.decode(123);
152
// Left([ValidationError]) - not a string
153
```
154
155
### Nullable to Default
156
157
Creates codecs that replace null/undefined inputs with a default value, otherwise uses the original codec.
158
159
```typescript { .api }
160
/**
161
* Creates a codec that replaces null/undefined with a default value
162
* @param codec - The original codec for non-null values
163
* @param a - The default value to use for null/undefined inputs
164
* @param name - Optional name for the codec
165
* @returns Codec that handles nullable inputs with defaults
166
*/
167
function fromNullable<C extends t.Mixed>(
168
codec: C,
169
a: t.TypeOf<C>,
170
name?: string
171
): C;
172
```
173
174
**Usage Examples:**
175
176
```typescript
177
import { fromNullable } from "io-ts-types";
178
import * as t from "io-ts";
179
180
// String with default for null/undefined
181
const StringWithDefault = fromNullable(t.string, "default");
182
183
const result1 = StringWithDefault.decode("hello");
184
// Right("hello")
185
186
const result2 = StringWithDefault.decode(null);
187
// Right("default")
188
189
const result3 = StringWithDefault.decode(undefined);
190
// Right("default")
191
192
const result4 = StringWithDefault.decode(123);
193
// Left([ValidationError]) - number is not string or null/undefined
194
195
// Number array with empty array default
196
const NumberArrayWithDefault = fromNullable(t.array(t.number), []);
197
198
const result5 = NumberArrayWithDefault.decode([1, 2, 3]);
199
// Right([1, 2, 3])
200
201
const result6 = NumberArrayWithDefault.decode(null);
202
// Right([]) - empty array default
203
```
204
205
### Codec from Refinement
206
207
Creates codecs from type guard functions (refinements), enabling custom type validation logic.
208
209
```typescript { .api }
210
/**
211
* Creates a codec from a refinement (type guard) function
212
* @param name - Name for the codec
213
* @param is - Type guard function that determines if input matches type A
214
* @returns Codec that validates using the type guard
215
*/
216
function fromRefinement<A>(
217
name: string,
218
is: (u: unknown) => u is A
219
): t.Type<A, A, unknown>;
220
```
221
222
**Usage Examples:**
223
224
```typescript
225
import { fromRefinement } from "io-ts-types";
226
227
// Custom type guard for positive numbers
228
function isPositiveNumber(u: unknown): u is number {
229
return typeof u === "number" && u > 0;
230
}
231
232
const PositiveNumber = fromRefinement("PositiveNumber", isPositiveNumber);
233
234
const result1 = PositiveNumber.decode(5);
235
// Right(5)
236
237
const result2 = PositiveNumber.decode(-1);
238
// Left([ValidationError])
239
240
const result3 = PositiveNumber.decode("5");
241
// Left([ValidationError]) - string, not number
242
243
// Custom type guard for non-empty arrays
244
function isNonEmptyArray<T>(u: unknown): u is T[] {
245
return Array.isArray(u) && u.length > 0;
246
}
247
248
const NonEmptyArray = fromRefinement("NonEmptyArray", isNonEmptyArray);
249
250
const result4 = NonEmptyArray.decode([1, 2, 3]);
251
// Right([1, 2, 3])
252
253
const result5 = NonEmptyArray.decode([]);
254
// Left([ValidationError])
255
```
256
257
### Lens Generation
258
259
Creates monocle-ts Lens objects for each property of an interface or exact type codec, enabling functional updates.
260
261
```typescript { .api }
262
/**
263
* Interface for exact types that have lenses
264
*/
265
interface ExactHasLenses extends t.ExactType<HasLenses> {}
266
267
/**
268
* Union type for codecs that can have lenses generated
269
*/
270
type HasLenses = t.InterfaceType<any> | ExactHasLenses;
271
272
/**
273
* Creates monocle-ts Lens objects for each property of a codec
274
* @param codec - Interface or exact type codec
275
* @returns Object with lens for each property
276
*/
277
function getLenses<C extends HasLenses>(
278
codec: C
279
): { [K in keyof t.TypeOf<C>]: Lens<t.TypeOf<C>, t.TypeOf<C>[K]> };
280
```
281
282
**Usage Examples:**
283
284
```typescript
285
import { getLenses } from "io-ts-types";
286
import * as t from "io-ts";
287
288
const User = t.type({
289
id: t.number,
290
name: t.string,
291
email: t.string,
292
age: t.number
293
});
294
295
const userLenses = getLenses(User);
296
297
// Now you have lenses for each property
298
const user = { id: 1, name: "Alice", email: "alice@example.com", age: 25 };
299
300
// Update name using lens
301
const updatedUser = userLenses.name.set("Alice Smith")(user);
302
// { id: 1, name: "Alice Smith", email: "alice@example.com", age: 25 }
303
304
// Update age using lens
305
const olderUser = userLenses.age.modify((age) => age + 1)(user);
306
// { id: 1, name: "Alice", email: "alice@example.com", age: 26 }
307
308
// Get property using lens
309
const userName = userLenses.name.get(user);
310
// "Alice"
311
312
// Compose lenses for nested updates (if you had nested objects)
313
const Address = t.type({
314
street: t.string,
315
city: t.string,
316
zipCode: t.string
317
});
318
319
const UserWithAddress = t.type({
320
id: t.number,
321
name: t.string,
322
address: Address
323
});
324
325
const userWithAddressLenses = getLenses(UserWithAddress);
326
const addressLenses = getLenses(Address);
327
328
// Compose lenses for nested property access
329
const streetLens = userWithAddressLenses.address.compose(addressLenses.street);
330
```
331
332
### Output Transformation
333
334
Transforms the output type of a codec by applying a function to the encoded value.
335
336
```typescript { .api }
337
/**
338
* Transforms the output type of a codec
339
* @param codec - The original codec
340
* @param f - Function to transform the output
341
* @param name - Optional name for the codec
342
* @returns Codec with transformed output type
343
*/
344
function mapOutput<A, O, I, P>(
345
codec: t.Type<A, O, I>,
346
f: (p: O) => P,
347
name?: string
348
): t.Type<A, P, I>;
349
```
350
351
**Usage Examples:**
352
353
```typescript
354
import { mapOutput } from "io-ts-types";
355
import * as t from "io-ts";
356
357
// Transform number to string output
358
const NumberAsString = mapOutput(t.number, (n) => n.toString());
359
360
const result1 = NumberAsString.decode(42);
361
// Right(42) - still a number internally
362
363
const encoded1 = NumberAsString.encode(42);
364
// "42" - output transformed to string
365
366
// Transform date to ISO string output
367
const DateAsISO = mapOutput(t.type({ date: t.string }), (obj) => ({
368
...obj,
369
date: new Date(obj.date).toISOString()
370
}));
371
372
const dateObj = { date: "2023-12-25" };
373
const encoded2 = DateAsISO.encode(dateObj);
374
// { date: "2023-12-25T00:00:00.000Z" } - transformed to ISO string
375
```
376
377
### Custom Encoding
378
379
Creates a codec clone with a custom encode function, allowing specialized serialization logic.
380
381
```typescript { .api }
382
/**
383
* Creates a codec clone with a custom encode function
384
* @param codec - The original codec
385
* @param encode - Custom encoding function
386
* @param name - Optional name for the codec
387
* @returns Codec with custom encoding logic
388
*/
389
function withEncode<A, O, I, P>(
390
codec: t.Type<A, O, I>,
391
encode: (a: A) => P,
392
name?: string
393
): t.Type<A, P, I>;
394
```
395
396
**Usage Examples:**
397
398
```typescript
399
import { withEncode } from "io-ts-types";
400
import * as t from "io-ts";
401
402
// Custom encoding for user objects
403
const User = t.type({
404
id: t.number,
405
firstName: t.string,
406
lastName: t.string,
407
email: t.string
408
});
409
410
const UserWithFullName = withEncode(
411
User,
412
(user) => ({
413
...user,
414
fullName: `${user.firstName} ${user.lastName}`
415
})
416
);
417
418
const userData = {
419
id: 1,
420
firstName: "Alice",
421
lastName: "Smith",
422
email: "alice@example.com"
423
};
424
425
const result = UserWithFullName.decode(userData);
426
// Right({ id: 1, firstName: "Alice", lastName: "Smith", email: "alice@example.com" })
427
428
const encoded = UserWithFullName.encode(result.right);
429
// { id: 1, firstName: "Alice", lastName: "Smith", email: "alice@example.com", fullName: "Alice Smith" }
430
```
431
432
### Codec Cloning
433
434
Creates a shallow clone of a codec, preserving the original codec structure while allowing modifications.
435
436
```typescript { .api }
437
/**
438
* Creates a shallow clone of a codec
439
* @param t - The codec to clone
440
* @returns Cloned codec with same prototype and properties
441
*/
442
function clone<C extends t.Any>(t: C): C;
443
```
444
445
**Usage Examples:**
446
447
```typescript
448
import { clone } from "io-ts-types";
449
import * as t from "io-ts";
450
451
const OriginalString = t.string;
452
const ClonedString = clone(OriginalString);
453
454
// Both codecs work identically
455
const result1 = OriginalString.decode("hello");
456
const result2 = ClonedString.decode("hello");
457
// Both: Right("hello")
458
459
// Useful when you need to modify codec properties
460
const ModifiedString = clone(t.string);
461
ModifiedString.name = "CustomString";
462
```
463
464
### Newtype Integration
465
466
Creates codecs from newtypes using newtype-ts iso functions, enabling safe wrapping and unwrapping of carrier types.
467
468
```typescript { .api }
469
/**
470
* Creates a codec from a newtype using iso
471
* @param codec - The codec for the carrier type
472
* @param name - Optional name for the codec
473
* @returns Codec that wraps/unwraps the newtype
474
*/
475
function fromNewtype<N extends AnyNewtype = never>(
476
codec: t.Type<CarrierOf<N>, t.OutputOf<CarrierOf<N>>>,
477
name?: string
478
): t.Type<N, CarrierOf<N>, unknown>;
479
```
480
481
**Usage Examples:**
482
483
```typescript
484
import { fromNewtype } from "io-ts-types";
485
import * as t from "io-ts";
486
import { Newtype, iso } from "newtype-ts";
487
488
// Define a newtype for UserId
489
interface UserId extends Newtype<{ readonly UserId: unique symbol }, number> {}
490
491
// Create iso for the newtype
492
const userIdIso = iso<UserId>();
493
494
// Create codec using fromNewtype
495
const UserIdCodec = fromNewtype<UserId>(t.number);
496
497
const result1 = UserIdCodec.decode(123);
498
// Right(123 as UserId) - wrapped in newtype
499
500
const result2 = UserIdCodec.decode("invalid");
501
// Left([ValidationError]) - not a number
502
503
// Use with newtype operations
504
if (result1._tag === "Right") {
505
const userId = result1.right;
506
const rawNumber = userIdIso.unwrap(userId); // 123
507
const wrappedAgain = userIdIso.wrap(456); // 456 as UserId
508
}
509
```