or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

built-in-validators.mddata-transformation.mderror-handling.mdgeneric-types.mdindex.mdscope-modules.mdstring-processing.mdtype-creation.md
tile.json

data-transformation.mddocs/

Data Transformation

Powerful data transformation system for converting and validating data through processing pipelines with morphs, combinators, and type-safe operations.

Capabilities

Morph System

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" }

Pipeline Operations

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[]");

Type Combinators

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)]
);

Default Values

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()"
});

Conditional Transformations

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");

Array Transformations

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"
  });

Error Handling in Transformations

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"
});