Skip to main content

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.

Capabilityzorphy 2.0freezed 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 controlZorphyPreset.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 uses abstract class $Foo + a generated concrete Foo.
  • freezed's when/map helpers are answered in zorphy by Dart 3's native exhaustive switch on 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.