0
# Transforms
1
2
Bidirectional value encoding and decoding with full type safety for data serialization and processing. Transforms allow you to define how data should be converted between different representations while maintaining TypeScript type safety.
3
4
## Capabilities
5
6
### Transform Creation
7
8
#### Transform Function
9
10
Creates a transform type with encode/decode logic.
11
12
```typescript { .api }
13
/**
14
* Creates a transform type with encode/decode logic
15
* @param schema - Base schema to transform
16
* @returns TransformDecodeBuilder for chaining decode operation
17
*/
18
function Transform<T extends TSchema>(schema: T): TransformDecodeBuilder<T>;
19
20
interface TransformDecodeBuilder<T extends TSchema> {
21
/** Define decode transformation (from schema type to runtime type) */
22
Decode<U>(decode: (value: Static<T>) => U): TransformEncodeBuilder<T, U>;
23
}
24
25
interface TransformEncodeBuilder<T extends TSchema, U> {
26
/** Define encode transformation (from runtime type back to schema type) */
27
Encode<V>(encode: (value: U) => V): TTransform<T, U>;
28
}
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
import { Type, Value } from "@sinclair/typebox";
35
36
// Transform string to Date
37
const DateTransform = Type.Transform(Type.String({ format: 'date-time' }))
38
.Decode(value => new Date(value)) // string -> Date
39
.Encode(value => value.toISOString()); // Date -> string
40
41
// Transform string to number
42
const NumberTransform = Type.Transform(Type.String())
43
.Decode(value => parseFloat(value)) // string -> number
44
.Encode(value => value.toString()); // number -> string
45
46
// Transform with validation and normalization
47
const EmailTransform = Type.Transform(Type.String())
48
.Decode(value => value.toLowerCase().trim()) // string -> normalized string
49
.Encode(value => value); // identity encode
50
```
51
52
### Complex Transforms
53
54
#### Object Property Transforms
55
56
Transform individual properties within objects.
57
58
```typescript
59
// Transform an object with mixed types
60
const UserTransform = Type.Transform(Type.Object({
61
name: Type.String(),
62
birthDate: Type.String({ format: 'date' }),
63
settings: Type.String() // JSON string
64
}))
65
.Decode(value => ({
66
name: value.name,
67
birthDate: new Date(value.birthDate),
68
settings: JSON.parse(value.settings)
69
}))
70
.Encode(value => ({
71
name: value.name,
72
birthDate: value.birthDate.toISOString().split('T')[0],
73
settings: JSON.stringify(value.settings)
74
}));
75
76
// Usage
77
const rawData = {
78
name: "Alice",
79
birthDate: "1990-05-15",
80
settings: '{"theme":"dark","lang":"en"}'
81
};
82
83
const decoded = Value.Decode(UserTransform, rawData);
84
// Result: {
85
// name: "Alice",
86
// birthDate: Date object,
87
// settings: { theme: "dark", lang: "en" }
88
// }
89
90
const encoded = Value.Encode(UserTransform, decoded);
91
// Result: Back to original string-based format
92
```
93
94
#### Array Transforms
95
96
Transform arrays with element-level transformations.
97
98
```typescript
99
// Transform array of date strings to Date objects
100
const DateArrayTransform = Type.Transform(Type.Array(Type.String()))
101
.Decode(value => value.map(str => new Date(str)))
102
.Encode(value => value.map(date => date.toISOString()));
103
104
// Transform CSV-like data
105
const CsvRowTransform = Type.Transform(Type.String())
106
.Decode(value => value.split(',').map(s => s.trim()))
107
.Encode(value => value.join(', '));
108
```
109
110
#### Nested Transforms
111
112
Compose multiple transforms for complex data structures.
113
114
```typescript
115
// Individual transforms
116
const TimestampTransform = Type.Transform(Type.Number())
117
.Decode(value => new Date(value * 1000))
118
.Encode(value => Math.floor(value.getTime() / 1000));
119
120
const JsonTransform = Type.Transform(Type.String())
121
.Decode(value => JSON.parse(value))
122
.Encode(value => JSON.stringify(value));
123
124
// Composed transform
125
const EventTransform = Type.Transform(Type.Object({
126
id: Type.String(),
127
timestamp: Type.Number(),
128
data: Type.String(),
129
tags: Type.Array(Type.String())
130
}))
131
.Decode(value => ({
132
id: value.id,
133
timestamp: new Date(value.timestamp * 1000),
134
data: JSON.parse(value.data),
135
tags: value.tags
136
}))
137
.Encode(value => ({
138
id: value.id,
139
timestamp: Math.floor(value.timestamp.getTime() / 1000),
140
data: JSON.stringify(value.data),
141
tags: value.tags
142
}));
143
```
144
145
### Transform Operations
146
147
#### Decode Operation
148
149
Converts from schema representation to runtime representation.
150
151
```typescript { .api }
152
/**
153
* Decodes transformed values to runtime form
154
* @param schema - Transform schema
155
* @param value - Value in schema representation
156
* @returns Value in runtime representation
157
*/
158
function Decode<T extends TSchema>(schema: T, value: unknown): Static<T>;
159
```
160
161
**Usage Examples:**
162
163
```typescript
164
const StringToNumber = Type.Transform(Type.String())
165
.Decode(value => parseInt(value, 10))
166
.Encode(value => value.toString());
167
168
// Decode string to number
169
const decoded = Value.Decode(StringToNumber, "123");
170
// Result: 123 (number)
171
172
// Works with complex objects
173
const ComplexTransform = Type.Transform(Type.Object({
174
count: Type.String(),
175
active: Type.String()
176
}))
177
.Decode(value => ({
178
count: parseInt(value.count, 10),
179
active: value.active === 'true'
180
}))
181
.Encode(value => ({
182
count: value.count.toString(),
183
active: value.active.toString()
184
}));
185
186
const decodedObject = Value.Decode(ComplexTransform, {
187
count: "42",
188
active: "true"
189
});
190
// Result: { count: 42, active: true }
191
```
192
193
#### Encode Operation
194
195
Converts from runtime representation back to schema representation.
196
197
```typescript { .api }
198
/**
199
* Encodes values using schema transforms
200
* @param schema - Transform schema
201
* @param value - Value in runtime representation
202
* @returns Value in schema representation
203
*/
204
function Encode<T extends TSchema>(schema: T, value: Static<T>): unknown;
205
```
206
207
**Usage Examples:**
208
209
```typescript
210
const DateTransform = Type.Transform(Type.String())
211
.Decode(value => new Date(value))
212
.Encode(value => value.toISOString());
213
214
const now = new Date();
215
const encoded = Value.Encode(DateTransform, now);
216
// Result: "2024-01-15T10:30:00.000Z" (string)
217
218
// Encoding preserves the schema format
219
const roundTrip = Value.Decode(DateTransform, encoded);
220
// Result: Date object equal to original
221
```
222
223
### Validation with Transforms
224
225
Transforms integrate seamlessly with TypeBox validation.
226
227
```typescript
228
const ValidatedTransform = Type.Transform(Type.Object({
229
age: Type.String({ pattern: '^\\d+$' }), // Must be numeric string
230
email: Type.String({ format: 'email' })
231
}))
232
.Decode(value => ({
233
age: parseInt(value.age, 10),
234
email: value.email.toLowerCase()
235
}))
236
.Encode(value => ({
237
age: value.age.toString(),
238
email: value.email
239
}));
240
241
// Validation happens on the schema representation
242
const isValid = Value.Check(ValidatedTransform, {
243
age: "25", // Valid: numeric string
244
email: "USER@EXAMPLE.COM"
245
});
246
247
if (isValid) {
248
const decoded = Value.Decode(ValidatedTransform, {
249
age: "25",
250
email: "USER@EXAMPLE.COM"
251
});
252
// Result: { age: 25, email: "user@example.com" }
253
}
254
```
255
256
### Transform Error Handling
257
258
Handle errors gracefully in transform operations.
259
260
```typescript
261
const SafeJsonTransform = Type.Transform(Type.String())
262
.Decode(value => {
263
try {
264
return JSON.parse(value);
265
} catch (error) {
266
return null; // or throw a custom error
267
}
268
})
269
.Encode(value => {
270
if (value === null) return '{}';
271
return JSON.stringify(value);
272
});
273
274
// Error handling in transforms
275
const SafeNumberTransform = Type.Transform(Type.String())
276
.Decode(value => {
277
const num = parseFloat(value);
278
if (isNaN(num)) {
279
throw new Error(`Invalid number: ${value}`);
280
}
281
return num;
282
})
283
.Encode(value => value.toString());
284
285
try {
286
const result = Value.Decode(SafeNumberTransform, "not-a-number");
287
} catch (error) {
288
console.error("Transform failed:", error.message);
289
}
290
```
291
292
### Advanced Transform Patterns
293
294
#### Conditional Transforms
295
296
Apply different transformations based on value characteristics.
297
298
```typescript
299
const ConditionalTransform = Type.Transform(Type.String())
300
.Decode(value => {
301
// Parse as number if numeric, otherwise keep as string
302
const num = parseFloat(value);
303
return isNaN(num) ? value : num;
304
})
305
.Encode(value => {
306
return typeof value === 'number' ? value.toString() : value;
307
});
308
```
309
310
#### Transform Composition
311
312
Chain multiple transforms for complex processing pipelines.
313
314
```typescript
315
// Base transforms
316
const TrimTransform = Type.Transform(Type.String())
317
.Decode(value => value.trim())
318
.Encode(value => value);
319
320
const UppercaseTransform = Type.Transform(Type.String())
321
.Decode(value => value.toUpperCase())
322
.Encode(value => value.toLowerCase());
323
324
// Composed processing (conceptual - requires custom implementation)
325
function processString(input: string): string {
326
const trimmed = Value.Decode(TrimTransform, input);
327
const uppercased = Value.Decode(UppercaseTransform, trimmed);
328
return uppercased;
329
}
330
```
331
332
#### Database Integration
333
334
Transform between database representations and application models.
335
336
```typescript
337
// Database row to application model
338
const UserRecordTransform = Type.Transform(Type.Object({
339
id: Type.Number(),
340
name: Type.String(),
341
created_at: Type.String(),
342
settings_json: Type.String(),
343
is_active: Type.Number() // SQLite boolean as number
344
}))
345
.Decode(value => ({
346
id: value.id,
347
name: value.name,
348
createdAt: new Date(value.created_at),
349
settings: JSON.parse(value.settings_json),
350
isActive: value.is_active === 1
351
}))
352
.Encode(value => ({
353
id: value.id,
354
name: value.name,
355
created_at: value.createdAt.toISOString(),
356
settings_json: JSON.stringify(value.settings),
357
is_active: value.isActive ? 1 : 0
358
}));
359
```
360
361
## Transform Limitations
362
363
- **Compilation**: Transforms cannot be compiled with TypeCompiler
364
- **Performance**: Runtime overhead compared to direct validation
365
- **Complexity**: Complex transforms can be hard to debug
366
- **Reversibility**: Encode/decode operations should be reversible when possible
367
368
**Best Practices:**
369
370
```typescript
371
// Good: Simple, reversible transform
372
const GoodTransform = Type.Transform(Type.String())
373
.Decode(value => parseInt(value, 10))
374
.Encode(value => value.toString());
375
376
// Avoid: Complex, non-reversible transforms
377
const AvoidTransform = Type.Transform(Type.String())
378
.Decode(value => {
379
// Complex processing that loses information
380
return value.split(',').filter(x => x.length > 0).length;
381
})
382
.Encode(value => value.toString()); // Can't recreate original
383
```
384
385
## Type Interfaces
386
387
```typescript { .api }
388
interface TTransform<T extends TSchema, U> extends TSchema {
389
[TransformKind]: 'Transform';
390
type: T['type'];
391
decode: (value: Static<T>) => U;
392
encode: (value: U) => Static<T>;
393
}
394
395
interface TransformDecodeBuilder<T extends TSchema> {
396
Decode<U>(decode: (value: Static<T>) => U): TransformEncodeBuilder<T, U>;
397
}
398
399
interface TransformEncodeBuilder<T extends TSchema, U> {
400
Encode<V>(encode: (value: U) => V): TTransform<T, U>;
401
}
402
403
// Transform decode/encode function types
404
type TransformFunction<T, U> = (value: T) => U;
405
type DecodeTransformFunction<T extends TSchema, U> = TransformFunction<Static<T>, U>;
406
type EncodeTransformFunction<T, U extends TSchema> = TransformFunction<T, Static<U>>;
407
```