Improved json serialization and data classes with full support for generics, inheritance, customization and more.
MIT License
Bot releases are visible (Hide)
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});
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
}
}
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();
}
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.
Published by schultek over 1 year ago
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});
When you define a class, any record field will be automatically included in the serialization.
@MappableClass()
class Location with PersonMappable {
const Position({
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
}
}
Additionally, you can create record type aliases using annotated typedefs:
@MappableRecord()
typedef Coordinates = ({double lat, double lng});
This 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.
var coords = CoordinatesMapper.fromJson('{"lat": 123, "lng": 456}');
// Using the generated extension methods.
var json = coords.toJson();
}
You can also annotate individual fields of a record to e.g. change the key:
@MappableRecord()
typedef FullName = (@MappableField(key: 'firstName') String, String);
This would serialize to:
{"firstName": "John", "$2": "Doe"}
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.
Published by schultek over 1 year ago
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 a Map<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
.
Published by schultek over 1 year ago
Breaking: Generated mappers no longer have a .container
property. This was removed in favor
of the new MapperContainer.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:
MyClassMapper.fromMap
or myClass.toMap()
) no additionalMapperContainer.globals.fromMap<MyClass>()
) theMyClassMapper.ensureInitialized()
.Breaking: Changed internal mapper implementation which causes any custom mapper to break.
MapperElementBase
class.MappingContext
being passed to mapper methods.See docs on how to use custom mappers in v3.
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
.
.copyWith.apply()
method to .copyWith.$update()
..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
to toValue
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.
Published by schultek almost 2 years ago
Mappers are now generated for each file containing annotated classes. This removes the
need to specify entry points in the build.yaml
.
This is now similar to how packages like
json_serializable
orfreezed
generate code.
part
files and need to be included as such.<MyClass>Mappable
mixin.Mapper
each class has its own <ClassName>Mapper
.
@MappableLib(createCombinedContainer: true)
.@CustomMapper
annotation in favor of includeCustomMappers
property on @MappableClass()
.For a detailed migration guide, see this issue.
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>);