Overview
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)
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
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:
| Method | Async | On failure |
|---|---|---|
parse(value) | No | Throws ZemaException |
safeParse(value) | No | Returns ZemaFailure |
parseAsync(value) | Yes | Throws ZemaException |
safeParseAsync(value) | Yes | Returns 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:
| Method | Output type | Behaviour |
|---|---|---|
.optional() | Output? | null input passes through. |
.nullable() | Output? | Explicit null is valid. |
.withDefault(v) | Output | null input substitutes v. |
.catchError(fn) | Output | Failures 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
| Method | Output 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
| Method | Error 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
| Method | Condition | Error code |
|---|---|---|
.gte(n) | value >= n | too_small |
.lte(n) | value <= n | too_big |
.gt(n) | value > n | too_small_exclusive |
.lt(n) | value < n | too_big_exclusive |
.positive() | value > 0 | not_positive |
.negative() | value < 0 | not_negative |
.step(n) | value % n == 0 | not_multiple_of |
Double constraints
| Method | Condition | Error code |
|---|---|---|
.gte(n) | value >= n | too_small |
.lte(n) | value <= n | too_big |
.gt(n) | value > n | too_small_exclusive |
.lt(n) | value < n | too_big_exclusive |
.positive() | value > 0.0 | not_positive |
.negative() | value < 0.0 | not_negative |
.finite() | not NaN/Inf | not_finite |
Learning Path
Foundations:
- Primitives: string, integer, double, boolean, dateTime
- Objects: nested structures and typed output
- Arrays: list validation
Intermediate:
- Error Handling: ZemaResult, ZemaIssue, paths
- Transformations: transform, pipe, preprocess
- Refinements: refine, superRefine, refineAsync
Advanced:
- Coercion: form data, query strings, environment variables
- Composition: extend, pick, omit, makeStrict
- Lazy schemas: recursive and self-referential types