Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD

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.


Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD

Comments are closed.