Zema Logo
Schemas

Union & Literal

Validate values that match any one of several schemas with ZemaUnion and ZemaLiteral.

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

CodeTrigger
invalid_unionNo schema in the union matched.
invalid_literalInput does not equal the literal value.

API reference

z.union(schemas)

FieldTypeDescription
schemasList<ZemaSchema<dynamic, T>>Candidate schemas, tried in order.

z.literal(value)

FieldTypeDescription
valueTThe only accepted value.

Next steps

Copyright © 2026