0
# Codec System
1
2
Experimental unified codec system combining decoding and encoding operations with enhanced composability.
3
4
## Capabilities
5
6
### Codec Interface
7
8
The core Codec interface that combines Decoder and Encoder functionality.
9
10
```typescript { .api }
11
/**
12
* Codec interface combining decoding and encoding operations
13
* @template I - Input type for decoding
14
* @template O - Output type for encoding
15
* @template A - The runtime type
16
*/
17
interface Codec<I, O, A> extends Decoder<I, A>, Encoder<O, A> {}
18
```
19
20
### Constructor Functions
21
22
Functions for creating codecs from decoders and encoders.
23
24
```typescript { .api }
25
/**
26
* Create a codec from separate decoder and encoder
27
* @param decoder - The decoder for input validation
28
* @param encoder - The encoder for output transformation
29
*/
30
function make<I, O, A>(decoder: Decoder<I, A>, encoder: Encoder<O, A>): Codec<I, O, A>;
31
32
/**
33
* Create a codec from decoder with identity encoder
34
* @param decoder - The decoder to wrap
35
*/
36
function fromDecoder<I, A>(decoder: Decoder<I, A>): Codec<I, A, A>;
37
38
/**
39
* Create a literal value codec
40
* @param value - The literal value to encode/decode
41
*/
42
function literal<A extends Literal>(value: A): Codec<unknown, A, A>;
43
44
type Literal = string | number | boolean | null;
45
```
46
47
**Usage Examples:**
48
49
```typescript
50
import * as C from "io-ts/Codec";
51
import * as D from "io-ts/Decoder";
52
import * as E from "io-ts/Encoder";
53
54
// Create codec from decoder and encoder
55
const TimestampCodec = C.make(
56
D.number, // Decode from number
57
E.id<Date>().contramap((date: Date) => date.getTime()) // Encode Date to number
58
);
59
60
// Create codec from decoder only (identity encoding)
61
const StringCodec = C.fromDecoder(D.string);
62
63
// Literal codec
64
const StatusCodec = C.literal('active');
65
```
66
67
### Primitive Codecs
68
69
Built-in codecs for basic types.
70
71
```typescript { .api }
72
/** String codec */
73
const string: Codec<unknown, string, string>;
74
75
/** Number codec */
76
const number: Codec<unknown, number, number>;
77
78
/** Boolean codec */
79
const boolean: Codec<unknown, boolean, boolean>;
80
81
/** Unknown array codec */
82
const UnknownArray: Codec<unknown, Array<unknown>, Array<unknown>>;
83
84
/** Unknown record codec */
85
const UnknownRecord: Codec<unknown, Record<string, unknown>, Record<string, unknown>>;
86
```
87
88
### Combinator Functions
89
90
Functions for composing complex codecs.
91
92
```typescript { .api }
93
/**
94
* Create struct codec from property codecs
95
* @param properties - Object mapping property names to codecs
96
*/
97
function struct<P>(properties: { [K in keyof P]: Codec<unknown, P[K], P[K]> }): Codec<unknown, P, P>;
98
99
/**
100
* Create partial struct codec where all properties are optional
101
* @param properties - Object mapping property names to codecs
102
*/
103
function partial<P>(properties: { [K in keyof P]: Codec<unknown, P[K], P[K]> }): Codec<unknown, Partial<P>, Partial<P>>;
104
105
/**
106
* Create array codec with element codec
107
* @param item - Codec for array elements
108
*/
109
function array<O, A>(item: Codec<unknown, O, A>): Codec<unknown, Array<O>, Array<A>>;
110
111
/**
112
* Create record codec with value codec
113
* @param codomain - Codec for record values
114
*/
115
function record<O, A>(codomain: Codec<unknown, O, A>): Codec<unknown, Record<string, O>, Record<string, A>>;
116
117
/**
118
* Create tuple codec with component codecs
119
* @param components - Array of codecs for each tuple position
120
*/
121
function tuple<C extends ReadonlyArray<Codec<unknown, any, any>>>(
122
...components: C
123
): Codec<unknown, { [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;
124
125
/**
126
* Create sum codec for discriminated unions
127
* @param tag - Discriminant property name
128
*/
129
function sum<T extends string>(tag: T): <MS extends Record<string, Codec<unknown, any, any>>>(
130
members: MS
131
) => Codec<unknown, OutputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import * as C from "io-ts/Codec";
138
139
// Struct codec
140
const User = C.struct({
141
name: C.string,
142
age: C.number,
143
email: C.string
144
});
145
146
// Array codec
147
const Numbers = C.array(C.number);
148
149
// Record codec
150
const Scores = C.record(C.number);
151
152
// Tuple codec
153
const Point = C.tuple(C.number, C.number);
154
155
// Sum codec for discriminated unions
156
const Shape = C.sum('type')({
157
circle: C.struct({
158
type: C.literal('circle'),
159
radius: C.number
160
}),
161
rectangle: C.struct({
162
type: C.literal('rectangle'),
163
width: C.number,
164
height: C.number
165
})
166
});
167
```
168
169
### Advanced Combinators
170
171
More sophisticated codec combinators.
172
173
```typescript { .api }
174
/**
175
* Refine a codec with additional predicate
176
* @param predicate - Predicate function for refinement
177
* @param expected - Expected type description
178
*/
179
function refine<A, B extends A>(
180
predicate: Refinement<A, B>,
181
expected: string
182
): <I, O>(codec: Codec<I, O, A>) => Codec<I, O, B>;
183
184
/**
185
* Make codec nullable (accepts null values)
186
* @param codec - Base codec to make nullable
187
*/
188
function nullable<I, O, A>(codec: Codec<I, O, A>): Codec<I, O | null, A | null>;
189
190
/**
191
* Create lazy codec for recursive types
192
* @param f - Function that returns the codec
193
*/
194
function lazy<I, O, A>(f: () => Codec<I, O, A>): Codec<I, O, A>;
195
196
/**
197
* Make codec readonly
198
* @param codec - Base codec to make readonly
199
*/
200
function readonly<I, O, A>(codec: Codec<I, O, A>): Codec<I, Readonly<O>, Readonly<A>>;
201
202
/**
203
* Compose two codecs
204
* @param to - Target codec
205
*/
206
function compose<L, A, P, B>(
207
from: Codec<L, A, A>,
208
to: Codec<A, P, B>
209
): Codec<L, P, B>;
210
211
/**
212
* Intersect two codecs
213
* @param right - Second codec to intersect
214
*/
215
function intersect<IB, OB, B>(
216
right: Codec<IB, OB, B>
217
): <IA, OA, A>(left: Codec<IA, OA, A>) => Codec<IA & IB, OA & OB, A & B>;
218
219
/**
220
* Map left side with input for better error handling
221
* @param f - Function to transform errors with input
222
*/
223
function mapLeftWithInput<I>(
224
f: (input: I, error: DecodeError) => DecodeError
225
): <O, A>(codec: Codec<I, O, A>) => Codec<I, O, A>;
226
```
227
228
**Usage Examples:**
229
230
```typescript
231
import * as C from "io-ts/Codec";
232
import { pipe } from "fp-ts/function";
233
234
// Refined codec
235
const PositiveNumber = pipe(
236
C.number,
237
C.refine((n): n is number => n > 0, 'positive number')
238
);
239
240
// Nullable codec
241
const OptionalString = C.nullable(C.string);
242
243
// Recursive codec
244
interface Category {
245
name: string;
246
subcategories: Category[];
247
}
248
249
const Category: C.Codec<unknown, Category, Category> = C.lazy(() =>
250
C.struct({
251
name: C.string,
252
subcategories: C.array(Category)
253
})
254
);
255
256
// Composition
257
const StringToNumber = C.compose(
258
C.string,
259
C.make(
260
pipe(D.string, D.parse(s => {
261
const n = parseFloat(s);
262
return isNaN(n) ? left(`Invalid number: ${s}`) : right(n);
263
})),
264
E.id<number>().contramap(String)
265
)
266
);
267
```
268
269
### Type Utilities
270
271
Type extraction utilities for codecs.
272
273
```typescript { .api }
274
/** Extract input type from codec */
275
type InputOf<C> = C extends Codec<infer I, any, any> ? I : never;
276
277
/** Extract output type from codec */
278
type OutputOf<C> = C extends Codec<any, infer O, any> ? O : never;
279
280
/** Extract runtime type from codec */
281
type TypeOf<C> = C extends Codec<any, any, infer A> ? A : never;
282
```
283
284
### Functional Programming Support
285
286
Instances and utilities for functional programming.
287
288
```typescript { .api }
289
/** Module URI for HKT support */
290
const URI = 'io-ts/Codec';
291
292
/** Invariant functor instance */
293
const Invariant: Invariant1<typeof URI>;
294
295
/**
296
* Invariant map over codec
297
* @param f - Function from A to B
298
* @param g - Function from B to A
299
*/
300
function imap<I, O, A, B>(
301
f: (a: A) => B,
302
g: (b: B) => A
303
): (codec: Codec<I, O, A>) => Codec<I, O, B>;
304
```
305
306
## Advanced Usage Patterns
307
308
### Round-trip Encoding/Decoding
309
310
```typescript
311
import * as C from "io-ts/Codec";
312
import { pipe } from "fp-ts/function";
313
import { fold } from "fp-ts/Either";
314
315
// Create a codec that can round-trip between JSON and typed data
316
const UserCodec = C.struct({
317
id: C.number,
318
name: C.string,
319
createdAt: C.make(
320
pipe(
321
D.string,
322
D.parse(s => {
323
const date = new Date(s);
324
return isNaN(date.getTime()) ? left('Invalid date') : right(date);
325
})
326
),
327
E.id<Date>().contramap(d => d.toISOString())
328
)
329
});
330
331
// Decode from JSON
332
const jsonData = {
333
id: 123,
334
name: "Alice",
335
createdAt: "2023-01-01T00:00:00.000Z"
336
};
337
338
const decodeResult = UserCodec.decode(jsonData);
339
if (decodeResult._tag === 'Right') {
340
const user = decodeResult.right;
341
console.log('Decoded user:', user);
342
343
// Encode back to JSON-serializable format
344
const encoded = UserCodec.encode(user);
345
console.log('Encoded back:', encoded);
346
// Output: { id: 123, name: "Alice", createdAt: "2023-01-01T00:00:00.000Z" }
347
}
348
```
349
350
### API Request/Response Codecs
351
352
```typescript
353
import * as C from "io-ts/Codec";
354
355
// Request codec
356
const CreateUserRequest = C.struct({
357
name: C.string,
358
email: C.string,
359
age: C.number
360
});
361
362
// Response codec with additional server fields
363
const UserResponse = C.struct({
364
id: C.number,
365
name: C.string,
366
email: C.string,
367
age: C.number,
368
createdAt: C.string,
369
updatedAt: C.string
370
});
371
372
// API client function
373
async function createUser(request: C.TypeOf<typeof CreateUserRequest>) {
374
const encodedRequest = CreateUserRequest.encode(request);
375
376
const response = await fetch('/api/users', {
377
method: 'POST',
378
headers: { 'Content-Type': 'application/json' },
379
body: JSON.stringify(encodedRequest)
380
});
381
382
const responseData = await response.json();
383
const decodedResponse = UserResponse.decode(responseData);
384
385
if (decodedResponse._tag === 'Left') {
386
throw new Error('Invalid response format');
387
}
388
389
return decodedResponse.right;
390
}
391
```
392
393
### Configuration Management
394
395
```typescript
396
import * as C from "io-ts/Codec";
397
398
// Environment-specific codec
399
const DatabaseConfig = C.struct({
400
host: C.string,
401
port: C.number,
402
database: C.string,
403
ssl: C.boolean
404
});
405
406
const AppConfig = C.struct({
407
environment: C.union(
408
C.literal('development'),
409
C.literal('staging'),
410
C.literal('production')
411
),
412
database: DatabaseConfig,
413
features: C.partial({
414
enableAnalytics: C.boolean,
415
enableCache: C.boolean,
416
maxConnections: C.number
417
})
418
});
419
420
// Load and validate configuration
421
function loadConfig(): C.TypeOf<typeof AppConfig> {
422
const rawConfig = {
423
environment: process.env.NODE_ENV || 'development',
424
database: {
425
host: process.env.DB_HOST || 'localhost',
426
port: parseInt(process.env.DB_PORT || '5432'),
427
database: process.env.DB_NAME || 'myapp',
428
ssl: process.env.DB_SSL === 'true'
429
},
430
features: {
431
enableAnalytics: process.env.ENABLE_ANALYTICS === 'true',
432
enableCache: process.env.ENABLE_CACHE !== 'false'
433
}
434
};
435
436
const result = AppConfig.decode(rawConfig);
437
if (result._tag === 'Left') {
438
throw new Error(`Invalid configuration: ${D.draw(result.left)}`);
439
}
440
441
return result.right;
442
}
443
```