Union & Literal
Union
Accepts a value if any of the given schemas succeeds. Schemas are tried in order; the first match wins and remaining schemas are not evaluated.
Signature:
ZemaUnion<T> union<T>(List<ZemaSchema<dynamic, T>> schemas)
Basic usage
// Accept either a UUID string or a positive integer ID
final idSchema = z.union([
z.string().uuid(),
z.integer().positive(),
]);
idSchema.parse('550e8400-e29b-41d4-a716-446655440000'); // String
idSchema.parse(42); // int
idSchema.parse(true); // throws ZemaException: invalid_union
Ordering matters
The first matching schema wins. Place more specific schemas before broader ones.
// ✓ Correct — literal is checked before the broader z.string()
final roleSchema = z.union([
z.literal('admin'),
z.string(),
]);
roleSchema.parse('admin'); // 'admin' — matched by literal
roleSchema.parse('editor'); // 'editor' — matched by z.string()
// ✗ Wrong — z.string() always wins, literal is never reached
final broken = z.union([
z.string(),
z.literal('admin'),
]);
String | null pattern
final maybeString = z.union([
z.string(),
z.literal(null),
]);
maybeString.parse('hello'); // 'hello'
maybeString.parse(null); // null
maybeString.parse(42); // throws ZemaException: invalid_union
Mixed types
// ID can be a UUID string or an integer
final idSchema = z.union([
z.string().uuid(),
z.integer().positive(),
]);
// Response body is either a success payload or an error object
final responseSchema = z.union([
z.object({'data': z.string(), 'status': z.literal('ok')}),
z.object({'error': z.string(), 'status': z.literal('error')}),
]);
Error diagnostics
When all schemas fail, a single invalid_union issue is returned. Its meta contains full diagnostics:
final result = idSchema.safeParse(true);
// ZemaIssue(
// code: 'invalid_union',
// meta: {
// 'schemaCount': 2,
// 'receivedType': 'bool',
// 'unionErrors': [[...issues from schema 0...], [...issues from schema 1...]],
// }
// )
Literal
Accepts only a single exact value. Equality is tested with ==.
Signature:
ZemaLiteral<T> literal<T>(T value)
Basic usage
z.literal('admin').parse('admin'); // 'admin'
z.literal('admin').parse('user'); // throws ZemaException: invalid_literal
z.literal(42).parse(42); // 42
z.literal(42).parse(43); // throws ZemaException: invalid_literal
z.literal(true).parse(true); // true
z.literal(true).parse(1); // throws ZemaException: invalid_literal
Closed set of string values
Use z.union with literals to express a fixed set of allowed values with distinct types:
final statusSchema = z.union([
z.literal('pending'),
z.literal('active'),
z.literal('archived'),
]);
statusSchema.parse('active'); // 'active'
statusSchema.parse('unknown'); // throws ZemaException: invalid_union
For a plain string enumeration without distinct types, z.string().oneOf([...]) is shorter and produces a richer invalid_enum error:
z.string().oneOf(['pending', 'active', 'archived'])
Common patterns
ID field (UUID or integer)
final userIdSchema = z.union([
z.string().uuid(),
z.integer().positive(),
]);
Discriminated union (manual)
The discriminator fast-path is not yet implemented — schemas are always tried in order. For now, parse the discriminant field first and delegate manually:
final schema = z.object({
'type': z.string().oneOf(['circle', 'rectangle']),
'radius': z.double().positive().optional(),
'width': z.double().positive().optional(),
'height': z.double().positive().optional(),
}).refine(
(data) {
if (data['type'] == 'circle') return data['radius'] != null;
if (data['type'] == 'rectangle') {
return data['width'] != null && data['height'] != null;
}
return false;
},
message: 'Shape fields do not match the type',
);
Optional with fallback
final valueSchema = z.union([
z.integer().positive(),
z.string().regex(RegExp(r'^\d+$')).transform(int.parse),
]).withDefault(0);
valueSchema.parse(42); // 42
valueSchema.parse('10'); // 10
valueSchema.parse(null); // 0 (default)
Error codes
| Code | Trigger |
|---|---|
invalid_union | No schema in the union matched. |
invalid_literal | Input does not equal the literal value. |
API reference
z.union(schemas)
| Field | Type | Description |
|---|---|---|
schemas | List<ZemaSchema<dynamic, T>> | Candidate schemas, tried in order. |
z.literal(value)
| Field | Type | Description |
|---|---|---|
value | T | The only accepted value. |
Next steps
- Primitives: string, integer, double, boolean, dateTime
- Refinements: custom validation logic