smartstruct – dart bean mappings – the easy nullsafe way!
code generator for generating type-safe mappers in dart, inspired by https://mapstruct.org/
overview
- add smartstruct as a dependency, and smartstruct_generator as a dev_dependency
- create a mapper class
- annotate the class with @mapper
- run the build_runner
- use the generated mapper!
installation
add smartstruct as a dependency, and the generator as a dev_dependency.
https://pub.dev/packages/smartstruct
dependencies:
smartstruct: [version]
dev_dependencies:
smartstruct_generator: [version]
# add build runner if not already added
build_runner:
run the generator
dart run build_runner build
flutter packages pub run build_runner build
// or watch
flutter packages pub run build_runner watch
usage
create your beans.
class dog {
final string breed;
final int age;
final string name;
dog(this.breed, this.age, this.name);
}
class dogmodel {
final string breed;
final int age;
final string name;
dogmodel(this.breed, this.age, this.name);
}
to generate a mapper for these two beans, you need to create a mapper interface.
// dogmapper.dart
part 'dogmapper.mapper.g.dart';
@mapper()
abstract class dogmapper {
dog frommodel(dogmodel model);
}
once you ran the generator, next to your dog.mapper.dart a dog.mapper.g.dart will be generated.
dart run build_runner build
// dogmapper.mapper.g.dart
class dogmapperimpl extends dogmapper {
@override
dog frommodel(dogmodel model) {
dog dog = dog(model.breed, model.age, model.name);
return dog;
}
}
the mapper supports positional arguments, named arguments and property access via implicit and explicit setters.
case sensitivity
by default mapper generator works in case insensitivity manner.
class source {
final string username;
source(this.username);
}
class target {
final string username;
target({required this.username});
}
@mapper()
abstract class examplemapper {
target fromsource(source source);
}
as you can see, classes above got different field’s names (case) for username. because mappers are case insensitive by default, those classes are correctly mapped.
class examplemapperimpl extends examplemapper {
@override
target fromsource(source source) {
final target = target(username: source.username);
return target;
}
}
to create case sensitive mapper, you can add param casesensitivefields to @mapper annotation. case sensitive mapper is checking field’s names in case sensitive manner.
@mapper(casesensitivefields: true)
abstract class examplemapper {
target fromsource(source source);
}
explicit field mapping
if some fields do not match each other, you can add a mapping annotation on the method level, to change the behaviour of certain mappings.
class dog {
final string name;
dog(this.name);
}
class dogmodel {
final string dogname;
dogmodel(this.dogname);
}
@mapper()
class dogmapper {
@mapping(source: 'dogname', target: 'name')
dog frommodel(dogmodel model);
}
in this case, the field dogname of dogmodel will be mapped to the field name of the resulting dog
class dogmapperimpl extends dogmapper {
@override
dog frommodel(dogmodel model) {
dog dog = dog(model.dogname);
return dog;
}
}
function mapping
the source attribute can also be a function. this function will then be called with the source parameter of the mapper method as a parameter.
class dog {
final string name;
final string breed;
dog(this.name, this.breed);
}
class dogmodel {
final string name;
dogmodel(this.name);
}
@mapper()
class dogmapper {
static string randombreed(dogmodel model) => 'some random breed';
@mapping(source: randombreed, target: 'breed')
dog frommodel(dogmodel model);
}
will generate the following mapper.
class dogmapperimpl extends dogmapper {
@override
dog frommodel(dogmodel model) {
dog dog = dog(model.dogname, dogmapper.randombreed(model));
return dog;
}
}
nested bean mapping
nested beans can be mapped, by defining an additional mapper method for the nested bean.
// nestedmapper.dart
class nestedtarget {
final subnestedtarget subnested;
nestedtarget(this.subnested);
}
class subnestedtarget {
final string myproperty;
subnestedtarget(this.myproperty);
}
class nestedsource {
final subnestedsource subnested;
nestedsource(this.subnested);
}
class subnestedsource {
final string myproperty;
subnestedsource(this.myproperty);
}
@mapper()
abstract class nestedmapper {
nestedtarget frommodel(nestedsource model);
subnestedtarget fromsubclassmodel(subnestedsource model);
}
will generate the mapper
// nestedmapper.mapper.g.dart
class nestedmapperimpl extends nestedmapper {
@override
nestedtarget frommodel(nestedsource model) {
final nestedtarget = nestedtarget(fromsubclassmodel(model.subnested));
return nestedtarget;
}
@override
subnestedtarget fromsubclassmodel(subnestedsource model) {
final subnestedtarget = subnestedtarget(model.myproperty);
return subnestedtarget;
}
}
list support
lists will be mapped as new instances of a list, with help of the map method.
class source {
final list<int> intlist;
final list<sourceentry> entrylist;
source(this.intlist, this.entrylist);
}
class sourceentry {
final string prop;
sourceentry(this.prop);
}
class target {
final list<int> intlist;
final list<targetentry> entrylist;
target(this.intlist, this.entrylist);
}
class targetentry {
final string prop;
targetentry(this.prop);
}
@mapper()
abstract class listmapper {
target fromsource(source source);
targetentry fromsourceentry(sourceentry source);
}
will generate the mapper
class listmapperimpl extends listmapper {
@override
target fromsource(source source) {
final target = target(
source.intlist.map((e) => e).tolist(),
source.entrylist.map(fromsourceentry).tolist());
return target;
}
@override
targetentry fromsourceentry(sourceentry source) {
final targetentry = targetentry(source.prop);
return targetentry;
}
}
injectable
the mapper can be made a lazy injectable singleton, by setting the argument useinjection to true, in the mapper interface.
in this case you also need to add the injectable dependency, as described here. https://pub.dev/packages/injectable
make sure, that in the mapper file, you import the injectable dependency, before running the build_runner!
// dogmapper.dart
import 'package:injectable/injectable.dart';
@mapper(useinjectable = true)
abstract class dogmapper {
dog frommodel(dogmodel model);
}
// dogmapper.mapper.g.dart
@lazysingleton(as: dogmapper)
class dogmapperimpl extends dogmapper {...}
examples
please refer to the example package, for a list of examples and how to use the mapper annotation.
you can always run the examples by navigating to the examples package and executing the generator.
$ dart pub get
...
$ dart run build_runner build
roadmap
feel free to open a pull request, if you’d like to contribute.
or just open an issue, and i do my level best to deliver.
Comments are closed.