Advanced
Branded Types
Apply nominal typing to validated values with .brand<B>(), preventing accidental mixing of semantically different values that share the same primitive type.
Dart uses structural typing, two String values are interchangeable regardless of what they represent. .brand<B>() adds a phantom type parameter B that the compiler treats as distinct, preventing accidental mixing of semantically different values at compile time with zero runtime cost.
.brand<B>()
Wraps the output in Branded<O, B>, applying nominal typing to a value that would otherwise be structurally identical to other values of the same type.
Signature:
ZemaSchema<I, Branded<O, B>> brand<B>()
Basic usage
// Two abstract marker classes, never instantiated
abstract class _UserIdBrand {}
abstract class _TeamIdBrand {}
final userIdSchema = z.string().uuid().brand<_UserIdBrand>();
final teamIdSchema = z.string().uuid().brand<_TeamIdBrand>();
final userId = userIdSchema.parse('550e8400-…');
// userId is Branded<String, _UserIdBrand>
final teamId = teamIdSchema.parse('660f9511-…');
// teamId is Branded<String, _TeamIdBrand>
void greet(Branded<String, _UserIdBrand> id) { … }
greet(teamId); // compile-time error: wrong brand
Accessing the raw value
final userId = userIdSchema.parse(rawString);
db.fetchUser(userId.value); // String
Branded wraps .value and delegates ==, hashCode, and toString to it, there is no runtime overhead beyond the wrapper object.
Branded equality
Two Branded values are equal if and only if they carry the same Brand type parameter and their underlying values are equal:
final a = userIdSchema.parse('abc-…');
final b = userIdSchema.parse('abc-…');
a == b; // true; same brand, same value
Combining with other modifiers
// Branded optional
z.string().uuid().optional().brand<_UserIdBrand>()
API reference
.brand<B>()
| Description | |
|---|---|
B | Phantom brand type: typically an abstract class, never instantiated |
| Returns | ZemaSchema<I, Branded<O, B>> |
Branded.value | The underlying validated value |