Json Schema Type Builder with Static Type Resolution for TypeScript
—
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.
Creates a transform type with encode/decode logic.
/**
* Creates a transform type with encode/decode logic
* @param schema - Base schema to transform
* @returns TransformDecodeBuilder for chaining decode operation
*/
function Transform<T extends TSchema>(schema: T): TransformDecodeBuilder<T>;
interface TransformDecodeBuilder<T extends TSchema> {
/** Define decode transformation (from schema type to runtime type) */
Decode<U>(decode: (value: Static<T>) => U): TransformEncodeBuilder<T, U>;
}
interface TransformEncodeBuilder<T extends TSchema, U> {
/** Define encode transformation (from runtime type back to schema type) */
Encode<V>(encode: (value: U) => V): TTransform<T, U>;
}Usage Examples:
import { Type, Value } from "@sinclair/typebox";
// Transform string to Date
const DateTransform = Type.Transform(Type.String({ format: 'date-time' }))
.Decode(value => new Date(value)) // string -> Date
.Encode(value => value.toISOString()); // Date -> string
// Transform string to number
const NumberTransform = Type.Transform(Type.String())
.Decode(value => parseFloat(value)) // string -> number
.Encode(value => value.toString()); // number -> string
// Transform with validation and normalization
const EmailTransform = Type.Transform(Type.String())
.Decode(value => value.toLowerCase().trim()) // string -> normalized string
.Encode(value => value); // identity encodeTransform individual properties within objects.
// Transform an object with mixed types
const UserTransform = Type.Transform(Type.Object({
name: Type.String(),
birthDate: Type.String({ format: 'date' }),
settings: Type.String() // JSON string
}))
.Decode(value => ({
name: value.name,
birthDate: new Date(value.birthDate),
settings: JSON.parse(value.settings)
}))
.Encode(value => ({
name: value.name,
birthDate: value.birthDate.toISOString().split('T')[0],
settings: JSON.stringify(value.settings)
}));
// Usage
const rawData = {
name: "Alice",
birthDate: "1990-05-15",
settings: '{"theme":"dark","lang":"en"}'
};
const decoded = Value.Decode(UserTransform, rawData);
// Result: {
// name: "Alice",
// birthDate: Date object,
// settings: { theme: "dark", lang: "en" }
// }
const encoded = Value.Encode(UserTransform, decoded);
// Result: Back to original string-based formatTransform arrays with element-level transformations.
// Transform array of date strings to Date objects
const DateArrayTransform = Type.Transform(Type.Array(Type.String()))
.Decode(value => value.map(str => new Date(str)))
.Encode(value => value.map(date => date.toISOString()));
// Transform CSV-like data
const CsvRowTransform = Type.Transform(Type.String())
.Decode(value => value.split(',').map(s => s.trim()))
.Encode(value => value.join(', '));Compose multiple transforms for complex data structures.
// Individual transforms
const TimestampTransform = Type.Transform(Type.Number())
.Decode(value => new Date(value * 1000))
.Encode(value => Math.floor(value.getTime() / 1000));
const JsonTransform = Type.Transform(Type.String())
.Decode(value => JSON.parse(value))
.Encode(value => JSON.stringify(value));
// Composed transform
const EventTransform = Type.Transform(Type.Object({
id: Type.String(),
timestamp: Type.Number(),
data: Type.String(),
tags: Type.Array(Type.String())
}))
.Decode(value => ({
id: value.id,
timestamp: new Date(value.timestamp * 1000),
data: JSON.parse(value.data),
tags: value.tags
}))
.Encode(value => ({
id: value.id,
timestamp: Math.floor(value.timestamp.getTime() / 1000),
data: JSON.stringify(value.data),
tags: value.tags
}));Converts from schema representation to runtime representation.
/**
* Decodes transformed values to runtime form
* @param schema - Transform schema
* @param value - Value in schema representation
* @returns Value in runtime representation
*/
function Decode<T extends TSchema>(schema: T, value: unknown): Static<T>;Usage Examples:
const StringToNumber = Type.Transform(Type.String())
.Decode(value => parseInt(value, 10))
.Encode(value => value.toString());
// Decode string to number
const decoded = Value.Decode(StringToNumber, "123");
// Result: 123 (number)
// Works with complex objects
const ComplexTransform = Type.Transform(Type.Object({
count: Type.String(),
active: Type.String()
}))
.Decode(value => ({
count: parseInt(value.count, 10),
active: value.active === 'true'
}))
.Encode(value => ({
count: value.count.toString(),
active: value.active.toString()
}));
const decodedObject = Value.Decode(ComplexTransform, {
count: "42",
active: "true"
});
// Result: { count: 42, active: true }Converts from runtime representation back to schema representation.
/**
* Encodes values using schema transforms
* @param schema - Transform schema
* @param value - Value in runtime representation
* @returns Value in schema representation
*/
function Encode<T extends TSchema>(schema: T, value: Static<T>): unknown;Usage Examples:
const DateTransform = Type.Transform(Type.String())
.Decode(value => new Date(value))
.Encode(value => value.toISOString());
const now = new Date();
const encoded = Value.Encode(DateTransform, now);
// Result: "2024-01-15T10:30:00.000Z" (string)
// Encoding preserves the schema format
const roundTrip = Value.Decode(DateTransform, encoded);
// Result: Date object equal to originalTransforms integrate seamlessly with TypeBox validation.
const ValidatedTransform = Type.Transform(Type.Object({
age: Type.String({ pattern: '^\\d+$' }), // Must be numeric string
email: Type.String({ format: 'email' })
}))
.Decode(value => ({
age: parseInt(value.age, 10),
email: value.email.toLowerCase()
}))
.Encode(value => ({
age: value.age.toString(),
email: value.email
}));
// Validation happens on the schema representation
const isValid = Value.Check(ValidatedTransform, {
age: "25", // Valid: numeric string
email: "USER@EXAMPLE.COM"
});
if (isValid) {
const decoded = Value.Decode(ValidatedTransform, {
age: "25",
email: "USER@EXAMPLE.COM"
});
// Result: { age: 25, email: "user@example.com" }
}Handle errors gracefully in transform operations.
const SafeJsonTransform = Type.Transform(Type.String())
.Decode(value => {
try {
return JSON.parse(value);
} catch (error) {
return null; // or throw a custom error
}
})
.Encode(value => {
if (value === null) return '{}';
return JSON.stringify(value);
});
// Error handling in transforms
const SafeNumberTransform = Type.Transform(Type.String())
.Decode(value => {
const num = parseFloat(value);
if (isNaN(num)) {
throw new Error(`Invalid number: ${value}`);
}
return num;
})
.Encode(value => value.toString());
try {
const result = Value.Decode(SafeNumberTransform, "not-a-number");
} catch (error) {
console.error("Transform failed:", error.message);
}Apply different transformations based on value characteristics.
const ConditionalTransform = Type.Transform(Type.String())
.Decode(value => {
// Parse as number if numeric, otherwise keep as string
const num = parseFloat(value);
return isNaN(num) ? value : num;
})
.Encode(value => {
return typeof value === 'number' ? value.toString() : value;
});Chain multiple transforms for complex processing pipelines.
// Base transforms
const TrimTransform = Type.Transform(Type.String())
.Decode(value => value.trim())
.Encode(value => value);
const UppercaseTransform = Type.Transform(Type.String())
.Decode(value => value.toUpperCase())
.Encode(value => value.toLowerCase());
// Composed processing (conceptual - requires custom implementation)
function processString(input: string): string {
const trimmed = Value.Decode(TrimTransform, input);
const uppercased = Value.Decode(UppercaseTransform, trimmed);
return uppercased;
}Transform between database representations and application models.
// Database row to application model
const UserRecordTransform = Type.Transform(Type.Object({
id: Type.Number(),
name: Type.String(),
created_at: Type.String(),
settings_json: Type.String(),
is_active: Type.Number() // SQLite boolean as number
}))
.Decode(value => ({
id: value.id,
name: value.name,
createdAt: new Date(value.created_at),
settings: JSON.parse(value.settings_json),
isActive: value.is_active === 1
}))
.Encode(value => ({
id: value.id,
name: value.name,
created_at: value.createdAt.toISOString(),
settings_json: JSON.stringify(value.settings),
is_active: value.isActive ? 1 : 0
}));Best Practices:
// Good: Simple, reversible transform
const GoodTransform = Type.Transform(Type.String())
.Decode(value => parseInt(value, 10))
.Encode(value => value.toString());
// Avoid: Complex, non-reversible transforms
const AvoidTransform = Type.Transform(Type.String())
.Decode(value => {
// Complex processing that loses information
return value.split(',').filter(x => x.length > 0).length;
})
.Encode(value => value.toString()); // Can't recreate originalinterface TTransform<T extends TSchema, U> extends TSchema {
[TransformKind]: 'Transform';
type: T['type'];
decode: (value: Static<T>) => U;
encode: (value: U) => Static<T>;
}
interface TransformDecodeBuilder<T extends TSchema> {
Decode<U>(decode: (value: Static<T>) => U): TransformEncodeBuilder<T, U>;
}
interface TransformEncodeBuilder<T extends TSchema, U> {
Encode<V>(encode: (value: U) => V): TTransform<T, U>;
}
// Transform decode/encode function types
type TransformFunction<T, U> = (value: T) => U;
type DecodeTransformFunction<T extends TSchema, U> = TransformFunction<Static<T>, U>;
type EncodeTransformFunction<T, U extends TSchema> = TransformFunction<T, Static<U>>;Install with Tessl CLI
npx tessl i tessl/npm-sinclair--typebox