Zorphy vs Freezed
Both generate immutable data classes. Zorphy goes further — it is a full entity toolkit with a nested patch system, filter/query descriptors, compareTo diffs, and an AI-agent CLI/MCP server.
| Capability | zorphy 2.0 | freezed 3.2.5 |
|---|---|---|
| Immutable data classes + copyWith | ✅ | ✅ |
Function-based copyWith (copyWithFn) | ✅ opt-in (ZorphyPreset.full) | ❌ |
JSON + polymorphic discriminators + toJsonLean | ✅ | ✅ (no toJsonLean) |
| Sealed unions | ✅ sealed $$Base + Dart 3 pattern matching | ✅ via when/map helpers |
| Nested patch system (partial updates of deep graphs) | ✅ UserPatch()..withAddressPatch(...) | ❌ manual nested copyWith chains |
Filter/query descriptors (Field<E, T>) | ✅ | ❌ |
| compareTo diffs between instances | ✅ | ❌ |
| changeTo conversions between subtypes | ✅ | ❌ |
| Multiple interface inheritance with generics | ✅ | ❌ |
| Self-referencing types | ✅ | ✅ |
| CLI + MCP server for scaffolding | ✅ | ❌ |
| Output size control | ✅ ZorphyPreset.lean/standard/full + per-feature flags | ❌ |
| analyzer 14 support | ✅ >=13.0.0 <15.0.0 | ❌ caps analyzer <11.0.0 (as of 2026-07-30) |
Where freezed differs (honest notes)
- freezed's mixin pattern (
with _$Foo) keeps the annotated class itself concrete; zorphy usesabstract class $Foo+ a generated concreteFoo. - freezed's
when/maphelpers are answered in zorphy by Dart 3's native exhaustiveswitchon the sealed base class. - freezed's ecosystem is older and larger.
Side by side
1. Simple data class
freezed:
class User with _$User {
const factory User({required String id, required String name, String? email}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
zorphy (lean preset — no patch/filter/compareTo bloat):
(preset: ZorphyPreset.lean, generateJson: true)
abstract class $User {
String get id;
String get name;
String? get email;
}
2. Sealed union
freezed:
class Result with _$Result {
const factory Result.ok(String value) = Ok;
const factory Result.err(String message) = Err;
}
zorphy (real sealed hierarchy, exhaustive Dart 3 switch):
(explicitSubTypes: [$Ok, $Err])
abstract class $$Result {}
()
abstract class $Ok implements $$Result { String get value; }
()
abstract class $Err implements $$Result { String get message; }
String describe(Result r) => switch (r) {
Ok(:final value) => 'ok: $value',
Err(:final message) => 'err: $message', // exhaustive — no default needed
};
3. Nested partial update
freezed requires manual deep copyWith chains:
user.copyWith(address: user.address.copyWith(city: 'Berlin'));
zorphy's patch system composes:
final patch = ProfilePatch()
..withName('Ada')
..withAddressPatch(AddressPatch()..withCity('Berlin'));
final updated = patch.applyTo(profile);
All three zorphy examples are real, compilable code — see
zorphy/example/lib/comparison/ in the repo.
Migrating from freezed
Use the zorphy_migrator codemod — see the
migration guide.