Zema Logo
Getting Started

Why Zema?

Zema is a runtime validation library for Dart. This page defines the problem it solves and its position relative to the existing Dart ecosystem.

The Runtime Validation Gap

Dart's type system operates at compile time. It cannot verify the shape of data that arrives at runtime from external sources.

// The compiler sees Map<String, dynamic>. It cannot see the content.
final user = User.fromJson(response.data);

If response.data contains a String where an int is expected, the assignment compiles. The failure occurs at runtime, in production.


Failure Modes

Type mismatch

The backend returns a field with an incorrect type:

// API response
{
  "id": "123",     // String instead of int
  "age": "twenty"  // String instead of int
}

// Result: throws at runtime
// type 'String' is not a subtype of type 'int'

Missing required field

A new required field is added on the backend without a coordinated frontend update:

// API response — new field absent from the Dart model
{
  "id": 1,
  "email": "alice@example.com",
  "role": "admin"  // unknown field, silently ignored
}

The app processes invalid state without throwing. The field is lost.

Corrupted storage data

A bug writes a malformed document to Firestore or another persistence layer. Every subsequent read of that document fails at the deserialization step.


The Solution: Schema Validation

Zema intercepts data at the boundary between external sources and application logic. Validation runs before the data enters the type system.

final userSchema = z.object({
  'id': z.integer(),
  'email': z.string().email(),
  'age': z.integer().gte(18),
});

final result = userSchema.safeParse(response.data);

switch (result) {
  case ZemaSuccess(:final value):
    // value is Map<String, dynamic> — every field has been validated
    processUser(value);
  case ZemaFailure(:final errors):
    // errors is List<ZemaIssue> — each issue carries a code, message, and path
    for (final issue in errors) {
      logger.error('${issue.pathString}: ${issue.message}');
    }
}

All field failures are collected in a single pass. The result carries the full error list, not just the first failure.


Relation to Freezed and json_serializable

Freezed and json_serializable provide compile-time model generation. They do not validate values at runtime.

// Freezed — no validation, throws on unexpected types
final user = User.fromJson(invalidData); // throws

Zema and Freezed serve different layers of the same pipeline. They compose:

// 1. Validate the raw map
final result = userSchema.safeParse(apiData);

// 2. Construct the Freezed model from the validated map
final user = result.mapTo(User.fromJson);

mapTo applies the constructor only if the result is a success. The Freezed model receives only valid, pre-checked data.


Comparison

CapabilityZemaFreezedjson_serializable
Runtime validationYesNoNo
Code generation requiredNoYesYes
API boundary validationYesNoNo
Form validationYesNoNo
Composable schemasYesNoNo
Hot reload impactNoneRequires build_runnerRequires build_runner

Applicable Contexts

Zema is applicable when:

  • External API responses must be validated before use.
  • Firestore documents may contain data written by older app versions.
  • Form input must be validated against rules that go beyond type checks.
  • A single schema definition must be reused across multiple validation sites.

Zema is not applicable when:

  • All data sources are fully controlled and trusted.
  • The validation overhead is measured and exceeds the acceptable budget.

Schema as Specification

A Zema schema is a machine-executable specification of a data contract. It documents constraints that no type annotation can express.

final userSchema = z.object({
  'id': z.integer(),
  'email': z.string().email(),
  'role': z.string().oneOf(['admin', 'editor', 'viewer']),
  'createdAt': z.dateTime(),
});

This schema states: id is an integer, email is a valid email address, role is one of three permitted strings, and createdAt is a parseable date/time. No external documentation is required.


Next Steps

Copyright © 2026