Dillon McMahon
2 min read

Categories

Tags

https://pub.dev/packages/dart_mappable

Many developers are familiar with the freezed package, but fewer have heard of dart_mappable. Both packages aim to bring data classes to Dart, but dart_mappable makes a few tradeoffs to offer significant benefits in return.

From the documentation:

dart_mappable covers all basic features (from/to json, == override, hashCode, toString(), copyWith) while adding new or improved support for advanced use-cases including generics, inheritance and polymorphism, customization, and more.

Additionally, it requires far less boilerplate. Let’s see it in action:

dart_mappable

1
2
3
4
5
6
7
8
part 'model.mapper.dart';

@MappableClass()
class MyClass with MyClassMappable {
  final int myValue;

  MyClass({required this.myValue});
}

vs

freezed

1
2
3
4
5
6
7
8
9
10
11
12
part 'model.freezed.dart';
part 'model.g.dart';

@freezed
class MyClass with _$MyClass {
  @JsonSerializable(explicitToJson: true)
  factory DocumentInfoLocalData({
    required final int myValue,
  }) = _MyClass;

  factory MyClass.fromJson(Map<String, dynamic> json) => _$MyClassFromJson(json);
}

Benefits

Boilerplate

With dart_mappable the code is a lot more concise and forgiving to Dart developers. It’s difficult to make a syntactic error with dart_mappable - just add @MappableClass() and with <class_name>Mappable. While with freezed, it is pretty easy to mess up all the syntactic nuances required final .., @Default(...) final .., _$, _, _$..FromJson, @JsonSerializable(explicitToJson: true), factory, etc. With dart_mappable you just write dart code and it takes care of the rest.

Custom Constructors and Flexibility

Conciseness is great, but functionality is also important. With dart_mappable, you are not restricted to a freezed-like constructor; you can use any Dart constructor you prefer:

1
2
3
4
5
6
7
8
MyClass(this.myValue);
const MyClass(this.myValue);
MyClass(this.myValue, [this.otherValue = 1]);
MyClass(this.myValue, {this.otherValue}): assert(myValue > 0); // not `@Assert('...')` needed
MyClass(this.myValue) {
    date = DateTime.now(); // Non-const default values :)
    // other logic
}

Generics and Inheritance

Dealing with generics is easy, since only regular dart code is needed, there’s no need to use additional annotations like @With or @Implements.

Drawbacks

Since you do not declare a fromJson constructor with dart_mappable it generates one for you, following the <class_name>Mapper convention.

1
MyClassMapper.fromJson(json);

This is really a style preference since with freezed you have to reference the class anyways (MyClass.fromJson(json)). This may become more useful if the lanugage team ever decides to support abstract static methods.

Another difference that some may consider a benefit or drawback is freezed tries to force immutability (unless you use @unfreezed). While dart_mapper leaves that it up to the user. Personally I always lean towards more power to the developer.

Conclusion

At the end of the day, dart_mappable gives Dart developers all the capabilities they usually choose freezed for, without getting in their way.

Looking ahead, both of these approaches might become obsolete when static metaprogramming lands in early 2025.