Zema Logo
Core

Overview

Architecture and entry points of the Zema validation engine.

The zema package is the validation engine. It provides schema construction, validation, error reporting, and data transformation with no code generation requirement.


Entry Point

All schema construction goes through the z constant (or its alias zema):

import 'package:zema/zema.dart';

// z is a const singleton of the Zema factory class
z.string()
z.integer()
z.object({})

Schema Layers

Primitive schemas

Type-strict validators for scalar Dart types.

z.string()     // String
z.integer()    // int
z.double()     // double
z.boolean()    // bool
z.dateTime()   // DateTime — also accepts String (ISO 8601) and int (Unix ms)

Primitives reference


Complex schemas

z.array(z.string())                                         // List<String>
z.object({'name': z.string()})                              // Map<String, dynamic>
z.objectAs({'name': z.string()}, (m) => User.fromJson(m))  // typed T
z.map(z.string(), z.integer())                              // Map<String, int>
z.union([z.string(), z.integer()])                          // String | int
z.literal('admin')                                          // exact value

Arrays | Objects


Special schemas

z.lazy(() => schema)    // self-referential / recursive schemas
z.custom((v) => v > 0)  // arbitrary predicate

Coercion sub-namespace

Coercion schemas convert compatible inputs before validating.

z.coerce().float()     // '3.14' → 3.14
z.coerce().boolean()   // 'true' | 1 → true
z.coerce().string()    // 42 → '42'

Parsing

Every schema exposes four parse methods:

MethodAsyncOn failure
parse(value)NoThrows ZemaException
safeParse(value)NoReturns ZemaFailure
parseAsync(value)YesThrows ZemaException
safeParseAsync(value)YesReturns ZemaFailure

safeParse is the standard entry point. parse is for contexts where a failure is a programming error and a hard crash is acceptable.

// safeParse — never throws
final result = schema.safeParse(data);

// parse — throws ZemaException on failure
final value = schema.parse(data);

ZemaResult

safeParse returns a sealed ZemaResult<T> with two variants.

switch (result) {
  case ZemaSuccess(:final value):
    use(value);
  case ZemaFailure(:final errors):
    for (final issue in errors) {
      print('${issue.pathString}: ${issue.message}');
    }
}

Functional alternative:

result.when(
  success: (value)  => use(value),
  failure: (errors) => report(errors),
);

Validation is exhaustive: all field failures are collected before returning. A single ZemaFailure contains the complete error list.


Modifiers

Every schema inherits these modifiers from ZemaSchema:

MethodOutput typeBehaviour
.optional()Output?null input passes through.
.nullable()Output?Explicit null is valid.
.withDefault(v)Outputnull input substitutes v.
.catchError(fn)OutputFailures caught; fn returns fallback.
z.string().optional()
z.string().withDefault('anon')
z.integer().gte(0).catchError((_) => 0)

Transformers

Transformers change the Output type of a schema.

// transform: map the validated output to a new type
z.string().transform((s) => s.toUpperCase())

// pipe: feed validated output into a second schema
z.string().transform(int.parse).pipe(z.integer().gte(0))

// preprocess: transform the raw input before validation
z.string().email().preprocess<dynamic>((v) => v?.toString().trim() ?? '')

Composability

Schemas are immutable values. Every fluent method returns a new instance.

final email = z.string().email();
final password = z.string().min(8);

final loginSchema = z.object({
  'email': email,
  'password': password,
});

final registerSchema = loginSchema.extend({
  'username': z.string().min(3).max(20),
  'age': z.integer().gte(18).optional(),
});

ZemaIssue

Each validation failure produces a ZemaIssue:

final class ZemaIssue {
  final String code;            // 'invalid_type', 'too_short', …
  final String message;         // localised human-readable description
  final List<Object> path;      // ['user', 'address', 'zip']
  final Object? receivedValue;
  final Map<String, dynamic>? meta;
}

issue.pathString formats the path: 'items.[2].name'.


Quick Reference

Factory methods

MethodOutput type
z.string()String
z.integer()int
z.double()double
z.boolean()bool
z.dateTime()DateTime
z.array(s)List<T>
z.object({})Map<String, dynamic>
z.objectAs({}, fn)T
z.map(k, v)Map<K, V>
z.union([...])T
z.literal(v)T

String constraints

MethodError code
.min(n)too_short
.max(n)too_long
.length(n)wrong_length
.regex(re)invalid_format
.email()invalid_email
.url()invalid_url
.uuid()invalid_uuid
.oneOf([...])invalid_enum
.trim()

Integer constraints

MethodConditionError code
.gte(n)value >= ntoo_small
.lte(n)value <= ntoo_big
.gt(n)value > ntoo_small_exclusive
.lt(n)value < ntoo_big_exclusive
.positive()value > 0not_positive
.negative()value < 0not_negative
.step(n)value % n == 0not_multiple_of

Double constraints

MethodConditionError code
.gte(n)value >= ntoo_small
.lte(n)value <= ntoo_big
.gt(n)value > ntoo_small_exclusive
.lt(n)value < ntoo_big_exclusive
.positive()value > 0.0not_positive
.negative()value < 0.0not_negative
.finite()not NaN/Infnot_finite

Learning Path

Foundations:

  1. Primitives: string, integer, double, boolean, dateTime
  2. Objects: nested structures and typed output
  3. Arrays: list validation

Intermediate:

  1. Error Handling: ZemaResult, ZemaIssue, paths
  2. Transformations: transform, pipe, preprocess
  3. Refinements: refine, superRefine, refineAsync

Advanced:

  1. Coercion: form data, query strings, environment variables
  2. Composition: extend, pick, omit, makeStrict
  3. Lazy schemas: recursive and self-referential types
Copyright © 2026