Skip to main content

Validation

Refinement

❗️ Important: Never throw inside a .refine() function — ZodArt will not catch it.

In addition to built-in validation methods like .min(), you can add your own validation logic using the .refine() method.

The .refine() method should return true if the value is valid, or false otherwise. When .refine() returns false, ZodArt creates a ZIssueCustom issue. You can optionally provide a message or code to include it in the issue.

See full example.

ℹ️ To return multiple issues or apply advanced validations use the .superRefine() method.

import 'package:zodart/zodart.dart';

part 'refine_method.zodart.dart';
part 'refine_method.zodart.type.dart';

/// Schema defined using ZodArt
.generateNewClass(outputClassName: 'Person')
abstract class PersonSchema {
/// Validates that:
/// - `firstName` is from 1 to 20 characters long
/// - `lastName` is from 1 to 30 characters long
/// - `validFrom` is a timestamp in seconds
/// - `validTo` is an optional timestamp in seconds
static final schema = (
firstName: ZString().min(1).max(20),
lastName: ZString().min(1).max(30),
validFrom: ZInt(),
validTo: ZInt().optional(),
);

// Access to generated helper methods like props list etc.
static const z = _PersonSchemaUtils();
static final ZObject<Person> zObject = z.zObject;
}

void main() {
// Refine the `personSchema` to ensure that `validFrom` < `validTo`
final refinedPersonSchema = PersonSchema.zObject.refine(
(person) {
final validTo = person.validTo;
return validTo == null || person.validFrom < validTo;
},
message: 'validFrom must be earlier than validTo.',
);

// Parse raw input (e.g. from an API, user form, etc.)
// ZodArt infers the type of `result.value` as `Person`
final result = refinedPersonSchema.parse({
'firstName': 'Zod',
'lastName': 'Art',
'validFrom': 1749952242,
'validTo': 631152000,
});

// Prints the custom error message 'validFrom must be earlier than validTo.'
print(result.issueSummary);
}

Cross-field validation

Refine and superRefine methods on ZObject run only after every field has been successfully parsed and validated. This can be limiting for form validation scenarios that depend on relationships between multiple fields.

Consider the following pseudo code:

/// User schema
final schema = (
name: ZString().max(5),
validFrom: ZDateTime(),
validTo: ZDateTime().nullable(),
);

/// Refine using `validFrom` and `validTo`
zUser.refine(
(user) {
final validTo = user.validTo;
return validTo == null || user.validFrom < validTo;
},
message: 'validFrom must be earlier than validTo.',
);

/// Only the issue for `[name]` is reported!
/// Since `refine` runs only after all fields are successfully parsed and validated
zUser.parse({
name: "Too long name", // Too long
validFrom: DateTime(2002),
validTo: DateTime(2000), // Should be after 2002
});

To address this limitation, cross-field validation support has been introduced for ZObject:

A cross-field validator is executed only when all fields it accesses (depends on) have been successfully parsed and validated.

  • If any field accessed within the validator has failed parsing or validation, the cross-field validator is skipped
  • If all accessed fields are valid, the validator is executed

This allows cross-field validation to:

  • run as soon as all dependent fields are valid
  • report cross-field errors even if unrelated fields fail the validation
  • operate only on reliable, validated data

With code generation

To define a cross-field validator for a ZObject:

  1. Create a top-level function that:

    • accepts a values accessor
    • returns SuperRefinerErrorRes on validation error or null on validation success

    The value accessor is autogenerated and is named as <SchemaClassName>FieldAccessor - e.g. for UserSchema the field accessor type name will be UserSchemaFieldAccessor.

    SuperRefinerErrorRes? <validatorName>(<FieldAccessor> values) {
    // To access field values use: `values.<fieldName>`
    }
  2. Register the validator using the ZodArt annotation:

    .generateNewClass(..., crossFieldValidators: [<validatorName>])
    abstract class UserSchema {
    // ...
    }
  3. Run code generation

🔗 See a full example with code generation

Without code generation

ZObject.withMapper accepts a list of crossValidators.

ZObject.withMapper(
schema,
fromJson: fromJson,
crossValidators: [...],
);