Zema Logo
Transformations

Transforms

Map validated output to a new type with .transform(), chain schemas with .pipe(), and understand execution order.

transform

Maps the validated output to a new type. The transformer function runs after validation succeeds. If the base schema fails, the transformer is never called.

Signature:

ZemaSchema<Input, T> transform<T>(T Function(Output) fn)

Execution order:

  1. Base schema validates input → Output.
  2. If validation fails → errors returned, transformer skipped.
  3. If validation succeeds → fn(output) called.
  4. If fn throws → transform_error issue returned.

Basic usage

final schema = z.string().transform((s) => s.toUpperCase());

schema.parse('hello');   // 'HELLO'
schema.parse(42);        // throws ZemaException: invalid_type

String to number

final schema = z.string()
    .regex(RegExp(r'^\d+$'), message: 'Must be numeric.')
    .transform(int.parse);

schema.parse('42');    // 42
schema.parse('abc');   // throws ZemaException: invalid_format (before transform)

String to DateTime

final schema = z.string()
    .regex(RegExp(r'^\d{4}-\d{2}-\d{2}'))
    .transform(DateTime.parse);

schema.parse('2024-02-08');   // DateTime(2024, 2, 8)

Or use z.dateTime() directly — it already handles ISO 8601 strings, Unix timestamps, and DateTime objects:

z.dateTime().parse('2024-02-08T10:30:00Z');   // DateTime
z.dateTime().parse(1707389400000);             // DateTime

Timestamp to DateTime

// Milliseconds since epoch
final msSchema = z.integer()
    .transform((ms) => DateTime.fromMillisecondsSinceEpoch(ms));

msSchema.parse(1707389400000);   // DateTime

// Seconds since epoch
final sSchema = z.integer()
    .transform((s) => DateTime.fromMillisecondsSinceEpoch(s * 1000));

String to Dart enum

enum UserRole { admin, user, guest }

final roleSchema = z.string()
    .oneOf(['admin', 'user', 'guest'])
    .transform((s) => UserRole.values.byName(s));

roleSchema.parse('admin');   // UserRole.admin

Map to typed model

final userSchema = z.object({
  'id': z.integer(),
  'email': z.string().email(),
}).transform((map) => User(
  id: map['id'] as int,
  email: map['email'] as String,
));

userSchema.parse({'id': 1, 'email': 'alice@example.com'});
// User(id: 1, email: 'alice@example.com')

For this pattern, z.objectAs() is a shorter alternative:

final userSchema = z.objectAs(
  {'id': z.integer(), 'email': z.string().email()},
  (map) => User(id: map['id'], email: map['email']),
);

Chaining transforms

Each .transform() call changes the output type. Transforms are applied in order:

final schema = z.string()
    .transform((s) => s.trim())
    .transform((s) => s.toLowerCase())
    .transform((s) => s.replaceAll(' ', '-'));

schema.parse('  Hello World  ');   // 'hello-world'

Array transformations

// Map elements
z.array(z.string())
    .transform((list) => list.map((s) => s.toUpperCase()).toList());

// Filter elements
z.array(z.integer())
    .transform((list) => list.where((n) => n > 0).toList());

// Reduce to a scalar
z.array(z.integer())
    .transform((list) => list.fold<int>(0, (sum, n) => sum + n));

// List → Map
z.array(z.object({'id': z.string(), 'name': z.string()}))
    .transform((list) => {for (final item in list) item['id']: item['name']});

CSV string

final csvSchema = z.string()
    .transform((s) => s.split(',').map((e) => e.trim()).toList());

csvSchema.parse('apple, banana, orange');
// ['apple', 'banana', 'orange']

Slug generation

final slugSchema = z.string()
    .transform((title) => title
        .toLowerCase()
        .trim()
        .replaceAll(RegExp(r'[^\w\s-]'), '')
        .replaceAll(RegExp(r'\s+'), '-')
        .replaceAll(RegExp(r'-+'), '-'));

slugSchema.parse('Hello World! 123');   // 'hello-world-123'

Transform error handling

If the transformer throws, transform_error is produced. Validate the input format first to ensure the transform is safe:

// ✓ Correct — validate format before transforming
final schema = z.string()
    .regex(RegExp(r'^\d+$'), message: 'Must be numeric.')
    .transform(int.parse);   // safe: regex ensures parse succeeds

// ✗ Risky — no validation, 'abc' will cause transform_error
final schema = z.string().transform(int.parse);

pipe

Chains two schemas in sequence. The output of the first schema becomes the input of the second.

Signature:

ZemaSchema<Input, T> pipe<T>(ZemaSchema<Output, T> next)

Execution order:

  1. First schema validates input → intermediate value.
  2. If first fails → errors returned, second skipped.
  3. Second schema validates the intermediate value → final output.

transform vs pipe

Use .transform() to map a value to a new type with no further validation. Use .pipe() when the intermediate value needs to pass through a full schema with its own rules.

// transform — simple mapping, no second schema
z.string().transform(int.parse)

// pipe — intermediate value goes through a real schema with constraints
z.string().transform(int.parse).pipe(z.integer().gte(0).lte(100))

Parse a numeric string and range-check

final portSchema = z.string()
    .regex(RegExp(r'^\d+$'))
    .transform(int.parse)
    .pipe(z.integer().gte(1).lte(65535));

portSchema.parse('8080');     // 8080
portSchema.parse('99999');    // throws ZemaException: too_big
portSchema.parse('abc');      // throws ZemaException: invalid_format

Validate string then coerce

// Validate the string first, then run it through the coerce schema
z.string().regex(RegExp(r'^\d+$'))
    .pipe(z.coerce().integer());

Date string → validated DateTime

final schema = z.string()
    .pipe(z.dateTime());  // z.dateTime() accepts ISO 8601 strings

schema.parse('2024-02-08T10:30:00Z');   // DateTime
schema.parse('not-a-date');             // throws ZemaException: invalid_date

Error codes

CodeTrigger
transform_errorThe transformer function threw an exception.

Plus any codes produced by the base schema (or the second schema in pipe).


API reference

.transform(fn)

ZemaSchema<Input, T> transform<T>(T Function(Output) fn)
Description
fnCalled with the validated output. If it throws, transform_error is returned.
ReturnsNew schema with output type T.

.pipe(next)

ZemaSchema<Input, T> pipe<T>(ZemaSchema<Output, T> next)
Description
nextA full schema that validates the current schema's output.
ReturnsNew schema with input type Input and output type T.
Copyright © 2026