0
# Modern Decoder API
1
2
Experimental modern decoder system with enhanced error reporting and functional composition patterns.
3
4
## Capabilities
5
6
### Decoder Interface
7
8
The core Decoder interface with functional programming patterns.
9
10
```typescript { .api }
11
/**
12
* Kleisli decoder interface for functional composition
13
* @template I - Input type
14
* @template A - Output type after successful decoding
15
*/
16
interface Decoder<I, A> {
17
readonly decode: (i: I) => Either<DecodeError, A>;
18
}
19
20
/** Decoder error type using FreeSemigroup for error accumulation */
21
type DecodeError = FreeSemigroup<DE.DecodeError<string>>;
22
```
23
24
### Error Handling
25
26
Enhanced error system with detailed decode error information.
27
28
```typescript { .api }
29
/**
30
* Create a decode error
31
* @param actual - The actual value that failed
32
* @param message - Error message
33
*/
34
function error(actual: unknown, message: string): DecodeError;
35
36
/**
37
* Create a successful decoder result
38
* @param value - The successful value
39
*/
40
function success<A>(value: A): Either<DecodeError, A>;
41
42
/**
43
* Create a failed decoder result
44
* @param error - The decode error
45
*/
46
function failure<A>(error: DecodeError): Either<DecodeError, A>;
47
```
48
49
### Constructor Functions
50
51
Functions for creating decoders from various sources.
52
53
```typescript { .api }
54
/**
55
* Create decoder from a refinement function
56
* @param refinement - Type refinement function
57
* @param expected - Expected type description
58
*/
59
function fromRefinement<I, A>(
60
refinement: Refinement<I, A>,
61
expected: string
62
): Decoder<I, A>;
63
64
/**
65
* Create decoder from a Guard
66
* @param guard - Guard instance
67
* @param expected - Expected type description
68
*/
69
function fromGuard<I, A>(guard: G.Guard<I, A>, expected: string): Decoder<I, A>;
70
71
/**
72
* Create a literal value decoder
73
* @param value - The literal value to match
74
*/
75
function literal<A extends Literal>(value: A): Decoder<unknown, A>;
76
77
type Literal = string | number | boolean | null;
78
```
79
80
### Primitive Decoders
81
82
Built-in decoders for basic types.
83
84
```typescript { .api }
85
/** String decoder */
86
const string: Decoder<unknown, string>;
87
88
/** Number decoder */
89
const number: Decoder<unknown, number>;
90
91
/** Boolean decoder */
92
const boolean: Decoder<unknown, boolean>;
93
94
/** Unknown array decoder */
95
const UnknownArray: Decoder<unknown, Array<unknown>>;
96
97
/** Unknown record decoder */
98
const UnknownRecord: Decoder<unknown, Record<string, unknown>>;
99
```
100
101
**Usage Examples:**
102
103
```typescript
104
import * as D from "io-ts/Decoder";
105
import { pipe } from "fp-ts/function";
106
import { fold } from "fp-ts/Either";
107
108
// Basic decoding
109
const result = D.string.decode("hello");
110
pipe(
111
result,
112
fold(
113
(error) => console.error("Decode failed:", D.draw(error)),
114
(value) => console.log("Success:", value)
115
)
116
);
117
118
// Literal decoding
119
const Status = D.literal('active');
120
const statusResult = Status.decode('active');
121
// result: Right('active')
122
```
123
124
### Combinator Functions
125
126
Functions for composing complex decoders.
127
128
```typescript { .api }
129
/**
130
* Create struct decoder from property decoders with typed inputs
131
* @param properties - Object mapping property names to decoders
132
*/
133
function fromStruct<P extends Record<string, Decoder<any, any>>>(
134
properties: P
135
): Decoder<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>;
136
137
/**
138
* Create struct decoder from property decoders (from unknown input)
139
* @param properties - Object mapping property names to decoders
140
*/
141
function struct<A>(properties: { [K in keyof A]: Decoder<unknown, A[K]> }): Decoder<unknown, A>;
142
143
/**
144
* Create partial struct decoder with typed inputs where all properties are optional
145
* @param properties - Object mapping property names to decoders
146
*/
147
function fromPartial<P extends Record<string, Decoder<any, any>>>(
148
properties: P
149
): Decoder<Partial<{ [K in keyof P]: InputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>>;
150
151
/**
152
* Create partial struct decoder where all properties are optional
153
* @param properties - Object mapping property names to decoders
154
*/
155
function partial<A>(properties: { [K in keyof A]: Decoder<unknown, A[K]> }): Decoder<unknown, Partial<A>>;
156
157
/**
158
* Create array decoder with typed inputs
159
* @param item - Decoder for array elements
160
*/
161
function fromArray<I, A>(item: Decoder<I, A>): Decoder<Array<I>, Array<A>>;
162
163
/**
164
* Create array decoder with element decoder
165
* @param item - Decoder for array elements
166
*/
167
function array<A>(item: Decoder<unknown, A>): Decoder<unknown, Array<A>>;
168
169
/**
170
* Create record decoder with typed inputs
171
* @param codomain - Decoder for record values
172
*/
173
function fromRecord<I, A>(codomain: Decoder<I, A>): Decoder<Record<string, I>, Record<string, A>>;
174
175
/**
176
* Create record decoder with value decoder
177
* @param codomain - Decoder for record values
178
*/
179
function record<A>(codomain: Decoder<unknown, A>): Decoder<unknown, Record<string, A>>;
180
181
/**
182
* Create tuple decoder with typed inputs
183
* @param components - Array of decoders for each tuple position
184
*/
185
function fromTuple<C extends ReadonlyArray<Decoder<any, any>>>(
186
...components: C
187
): Decoder<{ [K in keyof C]: InputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;
188
189
/**
190
* Create tuple decoder with component decoders
191
* @param components - Array of decoders for each tuple position
192
*/
193
function tuple<A extends ReadonlyArray<unknown>>(
194
...components: { [K in keyof A]: Decoder<unknown, A[K]> }
195
): Decoder<unknown, A>;
196
197
/**
198
* Create union decoder that tries multiple decoders
199
* @param members - Array of decoder alternatives
200
*/
201
function union<MS extends [Decoder<unknown, any>, ...Array<Decoder<unknown, any>>]>(
202
...members: MS
203
): Decoder<unknown, TypeOf<MS[number]>>;
204
205
/**
206
* Create intersection decoder that applies all decoders
207
* @param right - Second decoder to intersect
208
*/
209
function intersect<B>(right: Decoder<unknown, B>): <A>(left: Decoder<unknown, A>) => Decoder<unknown, A & B>;
210
211
/**
212
* Create tagged union decoder from member decoders with typed inputs
213
* @param tag - The discriminator property key
214
*/
215
function fromSum<T extends string>(
216
tag: T
217
): <MS extends Record<string, Decoder<any, any>>>(
218
members: MS
219
) => Decoder<InputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;
220
221
/**
222
* Create tagged union decoder from member decoders
223
* @param tag - The discriminator property key
224
*/
225
function sum<T extends string>(
226
tag: T
227
): <A>(members: { [K in keyof A]: Decoder<unknown, A[K] & Record<T, K>> }) => Decoder<unknown, A[keyof A]>;
228
```
229
230
### Deprecated Functions
231
232
The following functions are deprecated and should be avoided in new code:
233
234
```typescript { .api }
235
/**
236
* @deprecated Use fromStruct instead
237
*/
238
const fromType: typeof fromStruct;
239
240
/**
241
* @deprecated Use struct instead
242
*/
243
const type: typeof struct;
244
```
245
246
**Usage Examples:**
247
248
```typescript
249
import * as D from "io-ts/Decoder";
250
251
// Struct decoder
252
const User = D.struct({
253
name: D.string,
254
age: D.number,
255
email: D.string
256
});
257
258
const userData = User.decode({
259
name: "Alice",
260
age: 30,
261
email: "alice@example.com"
262
});
263
264
// Array decoder
265
const Numbers = D.array(D.number);
266
const numbersResult = Numbers.decode([1, 2, 3]);
267
268
// Union decoder
269
const StringOrNumber = D.union(D.string, D.number);
270
const unionResult1 = StringOrNumber.decode("hello");
271
const unionResult2 = StringOrNumber.decode(42);
272
273
// Tuple decoder
274
const Coordinate = D.tuple(D.number, D.number);
275
const coordResult = Coordinate.decode([10, 20]);
276
277
// Tagged union decoder
278
const Shape = D.sum('type')({
279
circle: D.struct({
280
type: D.literal('circle'),
281
radius: D.number
282
}),
283
rectangle: D.struct({
284
type: D.literal('rectangle'),
285
width: D.number,
286
height: D.number
287
})
288
});
289
290
const shapeResult = Shape.decode({ type: 'circle', radius: 5 });
291
```
292
293
### Advanced Combinators
294
295
More sophisticated decoder combinators for complex scenarios.
296
297
```typescript { .api }
298
/**
299
* Add custom error message to decoder failures
300
* @param message - Custom error message
301
*/
302
function withMessage<I>(message: string): <A>(decoder: Decoder<I, A>) => Decoder<I, A>;
303
304
/**
305
* Refine a decoder with additional predicate
306
* @param predicate - Predicate function for refinement
307
* @param expected - Expected type description
308
*/
309
function refine<A, B extends A>(
310
predicate: Refinement<A, B>,
311
expected: string
312
): (decoder: Decoder<unknown, A>) => Decoder<unknown, B>;
313
314
/**
315
* Parse decoder result with custom function
316
* @param parser - Function to transform decoded value (returns Either<DecodeError, B>)
317
*/
318
function parse<A, B>(
319
parser: (a: A) => Either<DecodeError, B>
320
): <I>(from: Decoder<I, A>) => Decoder<I, B>;
321
322
/**
323
* Make decoder nullable (accepts null values)
324
* @param decoder - Base decoder to make nullable
325
*/
326
function nullable<A>(decoder: Decoder<unknown, A>): Decoder<unknown, A | null>;
327
328
/**
329
* Create lazy decoder for recursive types
330
* @param f - Function that returns the decoder
331
*/
332
function lazy<A>(f: () => Decoder<unknown, A>): Decoder<unknown, A>;
333
334
/**
335
* Map left side with input for better error messages
336
* @param f - Function to transform errors with input context
337
*/
338
function mapLeftWithInput<I>(
339
f: (input: I, error: DecodeError) => DecodeError
340
): <A>(decoder: Decoder<I, A>) => Decoder<I, A>;
341
```
342
343
**Usage Examples:**
344
345
```typescript
346
import * as D from "io-ts/Decoder";
347
import { pipe } from "fp-ts/function";
348
349
// Custom error messages
350
const EmailWithMessage = pipe(
351
D.string,
352
D.refine((s): s is string => /\S+@\S+\.\S+/.test(s), 'valid email'),
353
D.withMessage('Please provide a valid email address')
354
);
355
356
// Parse transformation
357
const StringToNumber = pipe(
358
D.string,
359
D.parse((s) => {
360
const n = parseFloat(s);
361
return isNaN(n) ? left(`Cannot parse "${s}" as number`) : right(n);
362
})
363
);
364
365
// Nullable decoder
366
const OptionalName = D.nullable(D.string);
367
const result1 = OptionalName.decode("Alice"); // Right("Alice")
368
const result2 = OptionalName.decode(null); // Right(null)
369
370
// Recursive decoder
371
interface Category {
372
name: string;
373
subcategories: Category[];
374
}
375
376
const Category: D.Decoder<unknown, Category> = D.lazy(() =>
377
D.struct({
378
name: D.string,
379
subcategories: D.array(Category)
380
})
381
);
382
```
383
384
### Functional Composition
385
386
Functional programming utilities for decoder composition.
387
388
```typescript { .api }
389
/**
390
* Map over successful decoder results
391
* @param f - Transformation function
392
*/
393
function map<A, B>(f: (a: A) => B): (decoder: Decoder<unknown, A>) => Decoder<unknown, B>;
394
395
/**
396
* Alternative decoder (try second if first fails)
397
* @param that - Alternative decoder
398
*/
399
function alt<A>(that: () => Decoder<unknown, A>): (decoder: Decoder<unknown, A>) => Decoder<unknown, A>;
400
401
/**
402
* Compose two decoders
403
* @param to - Target decoder
404
*/
405
function compose<A, B>(to: Decoder<A, B>): (from: Decoder<unknown, A>) => Decoder<unknown, B>;
406
407
/**
408
* Identity decoder
409
*/
410
function id<A>(): Decoder<A, A>;
411
```
412
413
### Type Utilities and Instances
414
415
Type extraction and functional programming instances.
416
417
```typescript { .api }
418
/** Extract input type from decoder */
419
type InputOf<D> = D extends Decoder<infer I, any> ? I : never;
420
421
/** Extract output type from decoder */
422
type TypeOf<D> = D extends Decoder<any, infer A> ? A : never;
423
424
/** Module URI for HKT support */
425
const URI = 'io-ts/Decoder';
426
427
/** Functor instance */
428
const Functor: Functor1<typeof URI>;
429
430
/** Alt instance */
431
const Alt: Alt1<typeof URI>;
432
433
/** Category instance */
434
const Category: Category1<typeof URI>;
435
```
436
437
### Error Formatting
438
439
Utilities for formatting and displaying decode errors.
440
441
```typescript { .api }
442
/**
443
* Draw decode error as readable string
444
* @param error - The decode error to format
445
*/
446
function draw(error: DecodeError): string;
447
448
/**
449
* Format Either result as string
450
* @param result - Either result to stringify
451
*/
452
function stringify<A>(result: Either<DecodeError, A>): string;
453
```
454
455
**Usage Example:**
456
457
```typescript
458
import * as D from "io-ts/Decoder";
459
460
const User = D.struct({
461
name: D.string,
462
age: D.number
463
});
464
465
const result = User.decode({ name: 123, age: "thirty" });
466
467
if (result._tag === "Left") {
468
console.log("Formatted error:");
469
console.log(D.draw(result.left));
470
471
console.log("Stringified result:");
472
console.log(D.stringify(result));
473
}
474
```
475
476
## Advanced Usage Patterns
477
478
### Complex Validation Pipeline
479
480
```typescript
481
import * as D from "io-ts/Decoder";
482
import { pipe } from "fp-ts/function";
483
import { fold } from "fp-ts/Either";
484
485
const UserData = pipe(
486
D.struct({
487
name: D.string,
488
email: D.string,
489
age: D.number
490
}),
491
D.refine(
492
(user): user is typeof user => user.age >= 18,
493
'adult user'
494
),
495
D.withMessage('User must be an adult with valid contact info')
496
);
497
498
const processUser = (input: unknown) =>
499
pipe(
500
UserData.decode(input),
501
fold(
502
(error) => ({
503
success: false,
504
error: D.draw(error)
505
}),
506
(user) => ({
507
success: true,
508
user: {
509
...user,
510
displayName: user.name.toUpperCase()
511
}
512
})
513
)
514
);
515
```
516
517
### Tagged Union Decoding
518
519
```typescript
520
import * as D from "io-ts/Decoder";
521
522
const Shape = D.union(
523
D.struct({
524
type: D.literal('circle'),
525
radius: D.number
526
}),
527
D.struct({
528
type: D.literal('rectangle'),
529
width: D.number,
530
height: D.number
531
}),
532
D.struct({
533
type: D.literal('triangle'),
534
base: D.number,
535
height: D.number
536
})
537
);
538
539
const shapes = [
540
{ type: 'circle', radius: 5 },
541
{ type: 'rectangle', width: 10, height: 20 },
542
{ type: 'triangle', base: 8, height: 12 }
543
];
544
545
shapes.forEach(shape => {
546
const result = Shape.decode(shape);
547
if (result._tag === 'Right') {
548
console.log('Valid shape:', result.right);
549
}
550
});
551
```