Troubleshooting
invalid_type on a field that is present in the input
Symptom: safeParse returns an invalid_type issue for a field whose value is clearly in the map.
Common causes:
- The Dart runtime type of the value does not match the schema.
z.integer()rejectsdoublevalues even if the number has no fractional part :42.0is adouble, not anint. - JSON decoded with
jsonDecodeproducesintfor whole numbers anddoublefor fractional ones. If the server occasionally sends1.0for an integer field, the schema sees adouble.
Fix: Use z.coerce().integer() to accept compatible numeric types, or preprocess the value:
// Accept both int and whole-number double
z.preprocess(
(v) => v is double ? v.toInt() : v,
z.integer(),
);
StateError: Result is a failure when accessing .value
Symptom: Calling result.value on a ZemaResult throws a StateError.
Cause: .value throws when called on a ZemaFailure. Always check the result before accessing the value.
Fix: Guard with isSuccess, or use pattern matching:
// Guard
if (result.isSuccess) {
use(result.value);
}
// Pattern matching (exhaustive)
switch (result) {
case ZemaSuccess(:final value): use(value);
case ZemaFailure(:final errors): report(errors);
}
Schema always fails with invalid_type for Map input
Symptom: A ZemaObject schema rejects a Map with invalid_type: expected object, received Map<String, dynamic>.
Cause: This should not happen : ZemaObject checks value is Map, which is true for any Map. If you see this, the value passed to safeParse is not actually a Map at runtime. Check that JSON decoding completed before validation runs.
Diagnosis:
print(input.runtimeType); // should be _Map<String, dynamic>
final result = schema.safeParse(input);
print(result.errors.first.meta); // prints received type
Schema defined inside a function is slow
Symptom: Validation is noticeably slow when called in a loop or on every widget rebuild.
Cause: Calling z.object({...}) or z.string().email() inside a function constructs new schema instances on every invocation. For hot paths (request handlers, widget build methods, tight loops), this adds measurable overhead.
Fix: Move schema definitions to the top level or to a static final field:
// Wrong: rebuilt on every call
void validate(Map<String, dynamic> data) {
final schema = z.object({'email': z.string().email()});
schema.parse(data);
}
// Correct: defined once
final _schema = z.object({'email': z.string().email()});
void validate(Map<String, dynamic> data) {
_schema.parse(data);
}
refineAsync is never reached
Symptom: An async refinement is not called even when the input looks valid.
Cause: Zema runs all synchronous constraints first. If any synchronous check fails, async refinements are skipped entirely.
Fix: Ensure all synchronous constraints pass before testing async behaviour. In tests, use safeParseAsync() (not safeParse()) to trigger the async path:
final result = await schema.safeParseAsync(input);
ZemaException has no .message property
Symptom: Trying to access e.message on a caught ZemaException does not compile.
Cause: ZemaException does not have a single message field. It has issues : a List<ZemaIssue>. Each issue has its own message.
Fix:
try {
schema.parse(input);
} on ZemaException catch (e) {
// Single summary
print(e.issues.map((i) => i.message).join(', '));
// Per-field breakdown
for (final issue in e.issues) {
print('${issue.pathString}: ${issue.message}');
}
}
discriminatedBy() returns invalid_union for a valid input
Symptom: A discriminated union fails even though the input has the correct discriminator value and valid fields.
Cause: One of these:
- The discriminator field schema in the object is not a
ZemaLiteral.discriminatedBy()reads the literal value from the schema directly : onlyz.literal(value)works here. - The discriminator value in the input is a different Dart type than the literal.
z.literal('click')will not match the integer1. - The field name passed to
discriminatedBy()does not match the key in the shape.
Diagnosis:
// Confirm the discriminator value type
print(input['type'].runtimeType);
// Confirm the schema's literal value
final schema = z.object({'type': z.literal('click'), 'x': z.integer()});
final literal = schema.shape['type'] as ZemaLiteral;
print(literal.value.runtimeType); // should match input
brand() output is not accepted by a function expecting the branded type
Symptom: The compiler rejects a Branded<String, _MyBrand> value where Branded<String, _MyBrand> is expected.
Cause: The brand type _MyBrand is likely defined in a different scope than the function parameter. Two abstract class _MyBrand {} declarations in different files are distinct types, even if they have the same name.
Fix: Define each brand marker class in a single shared file and import it wherever the branded schema or the consuming function is used.
Error messages are in English even after calling setLocale()
Symptom: ZemaErrorMap.setLocale('fr') is called but error messages remain in English.
Cause: setLocale() takes effect for all subsequent safeParse() calls. If schemas were already parsed before the call, or if the call happens after the first validation, earlier results are unaffected.
Fix: Call ZemaErrorMap.setLocale() at application startup, before any schema is used. In Flutter, place it in main() before runApp():
void main() {
ZemaErrorMap.setLocale('fr');
runApp(const MyApp());
}
Type inference fails for z.union()
Symptom: The analyzer reports couldn't infer type parameter or argument type List<Object> can't be assigned.
Cause: When schemas in the union list have different output types (e.g. ZemaString and ZemaInt), Dart cannot infer a common T. The type parameter must be explicit.
Fix: Provide an explicit type argument:
// Fails to infer T
z.union([z.string(), z.integer()])
// Correct: explicit T
z.union<dynamic>([z.string(), z.integer()])
// No inference needed when all schemas share an output type
z.union([z.literal('a'), z.literal('b'), z.literal('c')]) // T = String