Powerful data transformation system for converting and validating data through processing pipelines with morphs, combinators, and type-safe operations.
Transform input data to different output types while maintaining type safety.
/**
* Morph function signature for data transformation
*/
interface InferredMorph<i = any, o extends Out = Out> {
(In: i): o;
}
/**
* Output wrapper for morph results
*/
interface Out<o = any> {
t: o;
introspectable: boolean;
}
/**
* Validated output wrapper (runtime-introspectable)
*/
interface To<o = any> extends Out<o> {
introspectable: true;
}Usage Examples:
import { type } from "arktype";
// Simple transformation
const ParseInt = type("string", "=>", (s: string) => parseInt(s));
ParseInt("42"); // Returns: 42
// With output validation
const SafeParseInt = type("string")
.pipe((s: string) => parseInt(s))
.to("number.integer");
// Object transformation
const ProcessUser = type({
name: "string",
age: ["string", "=>", (s: string) => parseInt(s)],
email: ["string", "=>", (s: string) => s.toLowerCase()]
});
const result = ProcessUser({
name: "John",
age: "25",
email: "JOHN@EXAMPLE.COM"
});
// Result: { name: "John", age: 25, email: "john@example.com" }Chain multiple transformations in sequence.
/**
* Create transformation pipeline
* @param morphs - Array of transformation functions
* @returns Type with chained transformations
*/
function pipe<morphs extends readonly Morph[]>(
...morphs: morphs
): Type<inferNaryPipe<morphs>>;
// Chaining support on Type instances
interface Type<t> {
/** Add transformation to existing type */
pipe<morph extends Morph>(
morph: morph,
outputType?: Type<any>
): Type<inferPipe<t, morph>>;
/** Validate transformation output */
to<outputDef>(outputDef: outputDef): Type<(In: distill.In<t>) => To<outputDef>>;
}Usage Examples:
// Multi-step pipeline
const ProcessString = type.pipe(
"string",
(s: string) => s.trim(),
(s: string) => s.toLowerCase(),
(s: string) => s.split(" ")
);
// Chained on existing type
const CleanString = type("string")
.pipe((s: string) => s.trim())
.pipe((s: string) => s.toLowerCase())
.to("string");
// Complex data processing
const ProcessApiData = type({
id: "string.uuid",
timestamp: "string.date.iso.parse",
data: ["string.json.parse", "=>", (obj: any) => obj.payload],
tags: ["string", "=>", (s: string) => s.split(",")]
});
// API response transformation
const ApiProcessor = type("string.json.parse")
.pipe((data: any) => data.results)
.pipe((results: any[]) => results.map(r => r.user))
.to("object[]");Combine types using logical operations with transformation support.
/**
* Union types (OR logic)
* @param defs - Type definitions to union
* @returns Type accepting any of the input types
*/
function or<defs extends readonly unknown[]>(
...defs: defs
): Type<defs[number]>;
/**
* Intersection types (AND logic)
* @param defs - Type definitions to intersect
* @returns Type requiring all input constraints
*/
function and<defs extends readonly unknown[]>(
...defs: defs
): Type<inferNaryIntersection<defs>>;
/**
* Object merging with precedence
* @param defs - Object type definitions to merge
* @returns Type with merged properties (later definitions override earlier)
*/
function merge<defs extends readonly unknown[]>(
...defs: defs
): Type<inferNaryMerge<defs>>;Usage Examples:
// Union with transformations
const StringOrNumberInput = type.or(
"string",
["number", "=>", (n: number) => n.toString()]
);
// Intersection with validation
const ValidatedUser = type.and(
{ name: "string", age: "number" },
{ email: "string.email" },
{ age: "number >= 18" } // Additional constraint
);
// Object merging
const BaseUser = type({ id: "string", name: "string" });
const UserWithEmail = type({ email: "string.email", verified: "boolean" });
const UserWithProfile = type({ bio: "string", avatar: "string.url" });
const FullUser = type.merge(BaseUser, UserWithEmail, UserWithProfile);
// Type with all properties, email and profile added to base
// Complex combination
const FlexibleInput = type.or(
"string",
"number",
["object", "=>", (obj: any) => JSON.stringify(obj)]
);Provide default values for missing or undefined data.
/**
* Default value syntax for properties
*/
type withDefault<t, v> = Default<t, v>;
interface Default<t = unknown, v = unknown> {
[defaultsToKey]: [t, v];
}
// Syntax: "type = defaultValue"
const WithDefaults = type({
name: "string",
age: "number = 0",
active: "boolean = true",
tags: "string[] = []"
});Usage Examples:
// Object with defaults
const UserConfig = type({
username: "string",
theme: "string = 'light'",
notifications: "boolean = true",
maxItems: "number.integer = 10"
});
const config = UserConfig({ username: "john" });
// Result: { username: "john", theme: "light", notifications: true, maxItems: 10 }
// Optional with defaults
const ApiOptions = type({
timeout: "number.integer = 5000",
retries: "number.integer = 3",
baseUrl: "string.url = 'https://api.example.com'"
});
// Transformation with defaults
const ProcessedData = type({
input: "string",
processed: ["string", "=>", (s: string) => s.toUpperCase()],
timestamp: "string.date.iso = new Date().toISOString()"
});Apply transformations based on input conditions.
// Using predicates for conditional logic
const ConditionalTransform = type("string | number")
.pipe((input: string | number) => {
if (typeof input === "string") {
return parseInt(input);
}
return input * 2;
})
.to("number");
// Pattern matching with morphs
const ProcessInput = type([
"string",
":",
(s: string) => s.length > 0
])
.pipe((s: string) => s.trim())
.to("string");Usage Examples:
// Conditional processing
const SmartProcessor = type("string | number | boolean")
.pipe((input: unknown) => {
if (typeof input === "string") return input.toUpperCase();
if (typeof input === "number") return input.toString();
if (typeof input === "boolean") return input ? "YES" : "NO";
return "UNKNOWN";
})
.to("string");
// Data normalization
const NormalizeId = type("string | number")
.pipe((id: string | number) => {
return typeof id === "string" ? id : id.toString();
})
.to("string");
// Validation with transformation
const ProcessAge = type("string | number")
.pipe((input: string | number) => {
const age = typeof input === "string" ? parseInt(input) : input;
if (age < 0 || age > 120) throw new Error("Invalid age");
return age;
})
.to("number.integer >= 0 <= 120");Transform array data with type-safe operations.
// Array processing patterns
const ProcessArray = type("string[]")
.pipe((strings: string[]) => strings.map(s => s.trim()))
.pipe((strings: string[]) => strings.filter(s => s.length > 0))
.to("string[]");
// Object array transformations
const ProcessUsers = type("object[]")
.pipe((users: any[]) => users.map(user => ({
...user,
name: user.name?.trim?.() || "Unknown",
email: user.email?.toLowerCase?.() || ""
})))
.to("object[]");Usage Examples:
// CSV processing
const ParseCsvRow = type("string")
.pipe((row: string) => row.split(","))
.pipe((fields: string[]) => fields.map(f => f.trim()))
.to("string[]");
// Data cleaning
const CleanUserArray = type({
users: ["object[]", "=>", (users: any[]) => users.map(user => ({
id: user.id || crypto.randomUUID(),
name: (user.name || "").trim(),
email: (user.email || "").toLowerCase(),
active: Boolean(user.active)
}))]
}).to({
users: [{
id: "string.uuid",
name: "string",
email: "string.email",
active: "boolean"
}]
});
// Aggregation
const SummarizeData = type("number[]")
.pipe((numbers: number[]) => ({
count: numbers.length,
sum: numbers.reduce((a, b) => a + b, 0),
average: numbers.length > 0 ? numbers.reduce((a, b) => a + b, 0) / numbers.length : 0
}))
.to({
count: "number.integer >= 0",
sum: "number",
average: "number"
});Handle errors and validation failures in transformation pipelines.
// Error handling in morphs
const SafeTransform = type("string")
.pipe((s: string, ctx) => {
try {
return JSON.parse(s);
} catch (error) {
return ctx.error("a valid JSON string");
}
})
.to("object");
// Validation within transformations
const ValidatedTransform = type("string")
.pipe((s: string, ctx) => {
const num = parseFloat(s);
if (isNaN(num)) {
return ctx.error("a numeric string");
}
if (num < 0) {
return ctx.error("a positive number");
}
return num;
})
.to("number >= 0");Usage Examples:
// Safe JSON parsing
const SafeJsonParse = type("string")
.pipe((s: string, ctx) => {
if (s.trim().length === 0) {
return ctx.error("a non-empty string");
}
try {
return JSON.parse(s);
} catch {
return ctx.error("valid JSON");
}
})
.to("object");
// Date parsing with validation
const ParseDate = type("string")
.pipe((s: string, ctx) => {
const date = new Date(s);
if (isNaN(date.getTime())) {
return ctx.error("a valid date string");
}
return date;
})
.to("Date");
// Multi-step validation
const ProcessUserInput = type({
email: ["string", "=>", (s: string, ctx) => {
const email = s.trim().toLowerCase();
if (!email.includes("@")) {
return ctx.error("a valid email address");
}
return email;
}],
age: ["string", "=>", (s: string, ctx) => {
const age = parseInt(s);
if (isNaN(age) || age < 0 || age > 150) {
return ctx.error("a valid age (0-150)");
}
return age;
}]
}).to({
email: "string.email",
age: "number.integer >= 0 <= 150"
});