TypeScript runtime type system for IO decoding/encoding
72
Experimental encoding system for transforming data between different representations.
The core Encoder interface for data transformation.
/**
* Encoder interface for transforming data to different representations
* @template O - Output type after encoding
* @template A - Input type to encode
*/
interface Encoder<O, A> {
readonly encode: (a: A) => O;
}Functions for creating and composing encoders.
/**
* Create struct encoder from property encoders
* @param encoders - Object mapping property names to encoders
*/
function struct<P>(encoders: { [K in keyof P]: Encoder<P[K], P[K]> }): Encoder<P, P>;
/**
* Create partial struct encoder where properties may be undefined
* @param encoders - Object mapping property names to encoders
*/
function partial<P>(encoders: { [K in keyof P]: Encoder<P[K], P[K]> }): Encoder<Partial<P>, Partial<P>>;
/**
* Create array encoder with element encoder
* @param item - Encoder for array elements
*/
function array<O, A>(item: Encoder<O, A>): Encoder<Array<O>, Array<A>>;
/**
* Create record encoder with value encoder
* @param codomain - Encoder for record values
*/
function record<O, A>(codomain: Encoder<O, A>): Encoder<Record<string, O>, Record<string, A>>;
/**
* Create tuple encoder with component encoders
* @param components - Array of encoders for each tuple position
*/
function tuple<C extends ReadonlyArray<Encoder<any, any>>>(
...components: C
): Encoder<{ [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;
/**
* Create sum encoder for discriminated unions
* @param tag - Discriminant property name
*/
function sum<T extends string>(tag: T): <MS extends Record<string, Encoder<any, any>>>(
members: MS
) => Encoder<OutputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;
/**
* Make encoder nullable
* @param encoder - Base encoder to make nullable
*/
function nullable<O, A>(encoder: Encoder<O, A>): Encoder<O | null, A | null>;
/**
* Create lazy encoder for recursive types
* @param f - Function that returns the encoder
*/
function lazy<O, A>(f: () => Encoder<O, A>): Encoder<O, A>;
/**
* Make encoder readonly
* @param encoder - Base encoder to make readonly
*/
function readonly<O, A>(encoder: Encoder<O, A>): Encoder<Readonly<O>, Readonly<A>>;
/**
* Intersect two encoders
* @param right - Second encoder to intersect
*/
function intersect<P, B>(
right: Encoder<P, B>
): <O, A>(left: Encoder<O, A>) => Encoder<O & P, A & B>;Usage Examples:
import * as E from "io-ts/Encoder";
// Struct encoder
const UserEncoder = E.struct({
name: E.id<string>(),
age: E.id<number>(),
email: E.id<string>()
});
// Array encoder
const NumbersEncoder = E.array(E.id<number>());
// Custom encoder that transforms dates to ISO strings
const DateToStringEncoder: E.Encoder<string, Date> = {
encode: (date: Date) => date.toISOString()
};
// Compose encoders
const TimestampedDataEncoder = E.struct({
data: E.id<string>(),
timestamp: DateToStringEncoder
});
const result = TimestampedDataEncoder.encode({
data: "hello",
timestamp: new Date("2023-01-01")
});
// result: { data: "hello", timestamp: "2023-01-01T00:00:00.000Z" }Functional programming utilities for encoder composition.
/**
* Contramap over encoder (transform input before encoding)
* @param f - Function to transform input
*/
function contramap<A, B>(f: (b: B) => A): <O>(encoder: Encoder<O, A>) => Encoder<O, B>;
/**
* Compose two encoders
* @param to - Target encoder
*/
function compose<E, A>(to: Encoder<E, A>): <O>(from: Encoder<A, O>) => Encoder<E, O>;
/**
* Identity encoder
*/
function id<A>(): Encoder<A, A>;Usage Examples:
import * as E from "io-ts/Encoder";
import { pipe } from "fp-ts/function";
// Contramap example: encode Person as string representation
interface Person {
firstName: string;
lastName: string;
age: number;
}
const PersonToStringEncoder = pipe(
E.id<string>(),
E.contramap((person: Person) =>
`${person.firstName} ${person.lastName} (${person.age})`
)
);
const person: Person = {
firstName: "Alice",
lastName: "Smith",
age: 30
};
const encoded = PersonToStringEncoder.encode(person);
// result: "Alice Smith (30)"
// Compose encoders
const NumberToStringEncoder = pipe(
E.id<string>(),
E.contramap(String)
);
const result = NumberToStringEncoder.encode(42);
// result: "42"Type extraction utilities for encoders.
/** Extract output type from encoder */
type OutputOf<E> = E extends Encoder<infer O, any> ? O : never;
/** Extract input type from encoder */
type TypeOf<E> = E extends Encoder<any, infer A> ? A : never;Instances for functional programming patterns.
/** Module URI for HKT support */
const URI = 'io-ts/Encoder';
/** Contravariant functor instance */
const Contravariant: Contravariant1<typeof URI>;
/** Category instance */
const Category: Category1<typeof URI>;import * as E from "io-ts/Encoder";
import { pipe } from "fp-ts/function";
// Transform complex objects for API serialization
interface User {
id: number;
profile: {
firstName: string;
lastName: string;
birthDate: Date;
};
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
interface UserApiFormat {
id: number;
fullName: string;
birthYear: number;
theme: string;
notificationsEnabled: boolean;
}
const UserToApiEncoder: E.Encoder<UserApiFormat, User> = {
encode: (user: User) => ({
id: user.id,
fullName: `${user.profile.firstName} ${user.profile.lastName}`,
birthYear: user.profile.birthDate.getFullYear(),
theme: user.preferences.theme,
notificationsEnabled: user.preferences.notifications
})
};
const user: User = {
id: 123,
profile: {
firstName: "Alice",
lastName: "Johnson",
birthDate: new Date("1990-05-15")
},
preferences: {
theme: 'dark',
notifications: true
}
};
const apiFormat = UserToApiEncoder.encode(user);
// result: {
// id: 123,
// fullName: "Alice Johnson",
// birthYear: 1990,
// theme: "dark",
// notificationsEnabled: true
// }import * as E from "io-ts/Encoder";
// Encode domain objects for database storage
interface Product {
id: string;
name: string;
price: number;
categories: string[];
metadata: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
interface ProductRow {
id: string;
name: string;
price_cents: number;
categories_json: string;
metadata_json: string;
created_at: string;
updated_at: string;
}
const ProductToRowEncoder: E.Encoder<ProductRow, Product> = {
encode: (product: Product) => ({
id: product.id,
name: product.name,
price_cents: Math.round(product.price * 100),
categories_json: JSON.stringify(product.categories),
metadata_json: JSON.stringify(product.metadata),
created_at: product.createdAt.toISOString(),
updated_at: product.updatedAt.toISOString()
})
};
const product: Product = {
id: "prod-123",
name: "Laptop",
price: 999.99,
categories: ["electronics", "computers"],
metadata: { brand: "TechCorp", model: "X1" },
createdAt: new Date("2023-01-01"),
updatedAt: new Date("2023-01-15")
};
const dbRow = ProductToRowEncoder.encode(product);import * as E from "io-ts/Encoder";
import { pipe } from "fp-ts/function";
// Convert between different date/time formats
const IsoDateEncoder = pipe(
E.id<string>(),
E.contramap((date: Date) => date.toISOString())
);
const UnixTimestampEncoder = pipe(
E.id<number>(),
E.contramap((date: Date) => Math.floor(date.getTime() / 1000))
);
const LocaleDateEncoder = pipe(
E.id<string>(),
E.contramap((date: Date) => date.toLocaleDateString())
);
// Create different representations of the same data
const now = new Date();
const formats = {
iso: IsoDateEncoder.encode(now),
unix: UnixTimestampEncoder.encode(now),
locale: LocaleDateEncoder.encode(now)
};
console.log(formats);
// Output: {
// iso: "2023-01-01T12:00:00.000Z",
// unix: 1672574400,
// locale: "1/1/2023"
// }import * as E from "io-ts/Encoder";
// Encode configuration objects for storage or transmission
interface AppConfig {
database: {
host: string;
port: number;
ssl: boolean;
};
features: {
enableAnalytics?: boolean;
maxConnections?: number;
};
environment: 'development' | 'staging' | 'production';
}
interface ConfigFile {
database_url: string;
analytics_enabled: boolean;
max_connections: number;
env: string;
}
const ConfigToFileEncoder: E.Encoder<ConfigFile, AppConfig> = {
encode: (config: AppConfig) => ({
database_url: `${config.database.ssl ? 'postgresql' : 'postgres'}://${config.database.host}:${config.database.port}`,
analytics_enabled: config.features.enableAnalytics ?? false,
max_connections: config.features.maxConnections ?? 10,
env: config.environment
})
};
const appConfig: AppConfig = {
database: {
host: "localhost",
port: 5432,
ssl: true
},
features: {
enableAnalytics: true,
maxConnections: 20
},
environment: 'production'
};
const configFile = ConfigToFileEncoder.encode(appConfig);
// result: {
// database_url: "postgresql://localhost:5432",
// analytics_enabled: true,
// max_connections: 20,
// env: "production"
// }Install with Tessl CLI
npx tessl i tessl/npm-io-tsdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10