Releases: schultek/dart_mappable
v4.0.0
Changelog
-
Require
sdk: >=3.0.0
. -
Added support for Records.
-
Fields of a class can now be any record type.
-
You can annotate toplevel record typedefs:
@MappableRecord() typedef Coordinates = ({double latitude, double longitude});
-
Documentation
Record Fields
dart_mappable
supports record fields out of the box.
When you define a class, any field using a record type will be automatically included in the serialization.
@MappableClass()
class Location with LocationMappable {
const Location({
required this.name,
required this.coordinates,
});
final (String, String) name;
final ({double lat, double lng}) coordinates;
}
An instance of this class would be encoded to:
{
"name": {
"$1": "John",
"$2": "Doe"
},
"coordinates": {
"lat": 123,
"lng": 456
}
}
Record Aliases
Additionally, you can create record type aliases using annotated typedefs:
@MappableRecord()
typedef Coordinates = ({double lat, double lng});
When using this type for a field of another class, it will be serialized as expected.
This also allows you to serialize records using the standalone generated mapper class as well as the generated extension methods:
void main() {
// Using the generated mapper class.
Coordinates coords = CoordinatesMapper.fromJson('{"lat": 123, "lng": 456}');
// Using the generated extension methods.
String json = coords.toJson();
}
Renaming Record Properties
You can also annotate individual fields of a record alias to change the key or add a hook:
@MappableRecord()
typedef FullName = (@MappableField(key: 'firstName') String, @MappableField(hook: MyHook()) String);
This will only work on annotated record type aliases, not inline record fields.
Note: Since typedefs are just an alias for a type, you cannot define two aliases for the same type
with different @MappableField()
modifiers. Only one will be used for serialization.
v3.1.1
Changelog
-
The builder now respects basic initializer expressions of a constructor. This makes it
possible to do field renaming or assigning to private fields without requiring an additional
getter matching the parameter.The following is now supported out of the box:
class MyClass { MyClass(int value, {String? name}) // Effectively renaming 'value' to 'data'. : data = value, // Assigning to a private field + having a null fallback. _name = name ?? 'Unnamed'; final int value; final String _name;
-
Fixed encoding of a map now returns a
Map<String, dynamic>
where possible
(instead of aMap<dynamic, dynamic>
) -
Fixed supporting expressions in
@MappableClass.includeCustomMappers
. -
Fixed error when using non-literal values in
@MappableValue()
. -
Fixed generic decoding when using generic superclass.
-
Fixed unknown-type bug for serialized non-constructor fields.
-
Fixed bug with undetermined
includeSubClasses
.
v3.0.0
Changelog
-
Breaking: Generated mappers no longer have a
.container
property. This was removed in favor
of the newMapperContainer.globals
container.// Instead of this: var value = MyClassMapper.container.fromValue(...); // Do this: var value = MapperContainer.globals.fromValue(...);
Mapper initialization and usage is now simplified to the following:
- When used explicitly (e.g. through
MyClassMapper.fromMap
ormyClass.toMap()
) no additional
initialization is needed. - When used implicitly (through a generic type e.g.
MapperContainer.globals.fromMap<MyClass>()
) the
mapper needs to be initialized once before being used withMyClassMapper.ensureInitialized()
.
- When used explicitly (e.g. through
-
Breaking: Changed internal mapper implementation which causes any custom mapper to break.
- Removed
MapperElementBase
class. - Added
MappingContext
being passed to mapper methods.
See docs on how to use custom mappers in v3.
- Removed
-
Breaking: Removed
@MappableLib.createCombinedContainer
in favor of@MappableLib.generateInitializerForScope
.Instead of generating a new container, v3 generates an initialization function for all mappers. Use it early on in your
application:@MappableLib(generateInitializerForScope: InitializerScope.package) library main; import 'main.init.dart'; void main() { initializeMappers(); ... }
-
Breaking: Improved support and features for
.copyWith
.- Copy-With now supports classes that implement multiple interfaces.
- Renamed
.copyWith.apply()
method to.copyWith.$update()
. - Added
.copyWith.$merge()
and.copyWith.$delta()
.
You can now use
.copyWith
with either an existing instance using.$merge
or a map of values using.$delta
.@MappableClass() class A with AMappable { A(this.a, this.b); int? a; int? b; } void main() { var a = A(1, null); var c = a.copyWith.$merge(A(null, 2)); assert(c == A(1, 2)); var d = a.copyWith.$delta({'b': 2}); assert(d == A(1, 2)); }
-
Breaking: Removed
CheckTypesHook
in favor of discriminator functions.You can now use a custom predicate function as the
discriminatorValue
of a class. This function can check
whether the encoded value should be decoded to this subclass and return a boolean.@MappableClass() abstract class A with AMappable { A(); } @MappableClass(discriminatorValue: B.checkType) class B extends A with BMappable { B(); /// checks if [value] should be decoded to [B] static bool checkType(value) { return value is Map && value['isB'] == true; } } @MappableClass(discriminatorValue: C.checkType) class C extends A with CMappable { C(); /// checks if [value] should be decoded to [C] static bool checkType(value) { return value is Map && value['isWhat'] == 'C'; } }
-
Added support for serializing fields that are not part of the constructor
when annotated with@MappableField()
. -
Added
EncodingOptions
totoValue
method. -
Added support for third-party models by using annotated
typedef
s. -
Added
renameMethods
to build options. -
Improved performance of generated encoding and decoding methods.
For a detailed migration guide, see this issue.
v2.0.0
-
Mappers are now generated for each file containing annotated classes. This removes the
need to specify entry points in thebuild.yaml
.This is now similar to how packages like
json_serializable
orfreezed
generate code.- Generated files are now
part
files and need to be included as such. - All annotated classes must now use their respective
<MyClass>Mappable
mixin. - Instead of one global
Mapper
each class has its own<ClassName>Mapper
.- A new global container that includes all models can now be generated using
@MappableLib(createCombinedContainer: true)
.
- A new global container that includes all models can now be generated using
- Mappers can be linked together to enable working with multiple classes.
- Removed
@CustomMapper
annotation in favor ofincludeCustomMappers
property on@MappableClass()
.
For a detailed migration guide, see this issue.
- Generated files are now
-
Documentation is now separated from the README using the official pub.dev documentation topics.
Find the new documentation here -
Improvements in performance and support for generics and inheritance.
-
Added the [CheckTypesHook] to allow for custom discriminator checks on subclasses in a
polymorphic class structure. -
CopyWith is now more powerful and also works for generic or polymorphic classes, while being
completely type-safe.When called on a superclass, the concrete subtype will be retained through a
.copyWith
call, which also respects generics:// with `class A` and `class B<T> extends A` A a = B<int>(); // static type A, dynamic type B<int> // signature will be `A copyWith()`, so static type A A a2 = a.copyWith(); // this will still resolve to a dynamic type of B<int> assert(a2 is B<int>);