Dart Code Generator for generating mapper classes

Overview

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.

// dog.mapper.dart
part 'dog.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
// dog.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.

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;
    }
}

Nested Bean Mapping

Nested beans can be mapped, by defining an additional mapper method for the nested bean.

// mapper.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

// 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

// dog.mapper.dart
@Mapper(useInjectable = true)
abstract class DogMapper {
    Dog fromModel(DogModel model);
}
// dog.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
  • Static mapping

    Static mapping

    For now we have to create mapper instance:

        final mapper = SomeMapperImpl();
        final entities = models.map(mapper.fromModel);
    

    It would be greate to use mappers as static

        final entities = models.map(SomeMapper.fromModel);
    

    Something like this:

    @Mapper()
    abstract class EntityMapper {
      static Entity fromModel(Model model) => _$EntityMapperFromModel(model);
    }
    
    opened by andrew-tpz 5
  • feat(optional): add support to map from optional to required property

    feat(optional): add support to map from optional to required property

    This is early version of the PR, i'd really enjoy feedback from you!

    Api Changes:

    • added optional defaultValue property in @Mapping annotation. (No breaking changes)

    Current state: Added support for mapping from optional to required property, if source value is null, defaultValue will be used, otherwise mapping will fail.

    Previous State: If tired to map from optional to required it generates uncompilable code

    Example:

    @Mapper
    abstract class NullablePropertiesMapper {
      @Mapping(source: 'number', target: 'number', defaultValue: '0')
      NullablePropertiesTarget fromSource(NullablePropertiesSource source);
    }
    

    will produce:

    class NullablePropertiesMapperImpl extends NullablePropertiesMapper {
      NullablePropertiesMapperImpl() : super();
    
      @override
      NullablePropertiesTarget fromSource(NullablePropertiesSource source) {
        assert(source.text != null, 'text cannot be blank');
        final nullablepropertiestarget =
            NullablePropertiesTarget(source.text!, source.number ?? 0);
        return nullablepropertiestarget;
      }
    }
    
    opened by jakubtrzcinski 5
  • Static Function Mapping for map Enums, List, Maps, Set not working

    Static Function Mapping for map Enums, List, Maps, Set not working

        @smotastic  Hi, thanks for info. 
    

    I've tested new commits and unfortunately there is still broken Static mapping.

    With this new change, it is not possible add helper methods for map Enums, List, Maps, Set or anything that is not a class as it generates invalid code

    static EnumFoo _mapEnumFoo(Source model) => model.foo ... 
    static List<String> _mapNames(Source model) => model.friends.map((f) => f.name).toList()
    

    both will generate something like

    EnumFoo _$_mapEnumFoo(Source model) {
       final enumFoo = EnumFoo._();
       return enumFoo;
    }
    

    for List it wil generate similar code.

    So for now within my fork I've completely reverted static mapping as it is not usable in my application.

    Originally posted by @petrnymsa in https://github.com/smotastic/smartstruct/issues/58#issuecomment-1214401368

    bug 1.3.0 
    opened by smotastic 4
  • Addded nullChecked support for nullable list

    Addded nullChecked support for nullable list

    I've added nullChecked for nullable list. (Test is failing due to an issue described below).

    Generated source is generating:

    final nullablelisttarget =
            NullableListTarget(source.list!.map((e) => e).toList());
    

    It should be

    final nullablelisttarget =
            NullableListTarget(source.list!.map(fromSubSource).toList());
    

    I'm not sure why matchingMappingListMethods.isNotEmpty is returning false.

    If you can help me out, I can make changes and push the commit on this PR. Also, let me know if there are any other changes related to this.

    opened by gopalsabhadiya 4
  • Mapper fails to generate

    Mapper fails to generate

    Mapper got case insensitive fields and contains fields: data and data. If you use a case-sensitive mapper, make sure the fields are unique in a case insensitive way.

    I'm using the latest version. This was still working v1.2.3+1

    opened by dustincatap 4
  • feat: static_proxy.

    feat: static_proxy.

    Generate static proxy for mapper class for the user can use the mapper without the mapper object.

    Which is discussed at https://github.com/smotastic/smartstruct/pull/57#discussion_r901207690.

    opened by skykaka 3
  • @Mapper(useInjection: true) not working, implementation class not being registered

    @Mapper(useInjection: true) not working, implementation class not being registered

    Given below the my mapper class:

    @Mapper(useInjection: true)
    abstract class UserSigninMapper {
      UserSigninContract? fromEntity(UserSigninEntity? entity);
    }
    

    and the generated file contains:

    @LazySingleton(as: UserSigninMapper)
    class UserSigninMapperImpl extends UserSigninMapper {
      @override
      UserSigninContract? fromEntity(UserSigninEntity? entity) {
        if (entity == null) {
          return null;
        }
        ;
        final usersignincontract =
            UserSigninContract(email: entity.email, password: entity.password);
        return usersignincontract;
      }
    }
    

    Running flutter pub run build_runner build shows the following output:

    [INFO] Running build...
    
    // Removed for brevity
    
    [AuthManagerImpl] depends on unregistered type [UserSigninMapper] from my_app/domain/mappers/authentication/user_signin_mapper.dart
    
    Did you forget to annotate the above class(s) or their implementation with @injectable?
    ------------------------------------------------------------------------ 
    
    // Removed for brevity
    
    [INFO] Succeeded after 19.2s with 14 outputs (90 actions)
    

    It seems that UserSigninMapper is not being registered. I think build_runner is ignoring generated files.

    opened by dustincatap 3
  • Support lists

    Support lists

    Lists should be mappable too.

    When the sourcefield contains an iterable, we iterate through it, and map it respectively to the target field. Primitive types will just map to itself, so we can also just create a new list with the old values in it. Lists of nested Beans will call the mapping method, provided in the mapper class.

    class NestedTarget {
      NestedTarget(this.subNested);
    
      final List<SubNestedTarget> subNested;
    }
    class NestedSource {
      NestedSource(this.subNested);
    
      final List<SubNestedSource> subNested;
    }
    
    @Mapper()
    abstract class NestedMapper {
      @Mapping(source: 'subNestedSource', target: 'subNestedTarget')
      NestedTarget fromModel(NestedSource model);
    
      SubNestedTarget fromSubClassModel(SubNestedSource model);
    }
    

    Should generate something like this.

    class NestedMapperImpl extends NestedMapper {
      @override
      NestedTarget fromModel(NestedSource model) {
        final nestedtarget = NestedTarget(model.subNested.map((e) => fromSubClassModel(e)).toList(););
        return nestedtarget;
      }
    
      @override
      SubNestedTarget fromSubClassModel(SubNestedSource model) {
        final subnestedtarget = SubNestedTarget(model.myProperty);
        return subnestedtarget;
      }
    }
    
    opened by smotastic 3
  • Add some function

    Add some function

    1. Retain the more information about source filed by "RefChain.dart"; When I use "object-box" and "smartstruct" at one time, I have some trouble.
    class Source {
        final one = ToOne<SubSource>() ;
    }
    
    class Target {
        late SubTarget one;
    }
    
    class SubSource {
    }
    
    class SubTarget {
    
    }
    
    @Mapper()
    abstract class SourceMapper {
    
        @Mapping(target: "one", source: "prop.one.target")
        Target fromSource(Source source);
    
        SubTarget fromSubSource(SubSource subSource);
    }
    

    The type of source "prop.code.target" is "SubSource" and the target "one" is "SubTarget", so it need the nested mapping "fromSubSource". But the code doesn't know the type of "prop.one.target", because it is a generic type in "ToOne" class. In the "SourceAssignment", the only type info of "prop.one.target" is "EntityT" which is a type variable in the "ToOne" class. It's impossible that finding the correct nested mapping for the "prop.one.target". The "RefChain" class can record the real type of "prop",“prop.one" and "prop.one.target". The "RefChain" also record the nullability of each of the property.

    1. Apply nested mapping after function mapping. Sometimes the result of function mapping need to mapped again by the nested mapping.
    class Source {
      late SubSourceRef source;
    }
    
    class Target {
      late SubTarget target;
    }
    
    class SubSource {
    }
    
    class SubTarget {
    }
    
    class SubSourceRef {
         late SubSource source;
    }
    
    @Mapper()
    abstract class ModelMapper {
    
      @Mapping(source: refToSource, target: 'source')
      Target fromSource(Source source);
      SubTarget fromSubSource(SubSource subSource);    
      
    
    }
    
    // The return value need to be mapped again by the nested mapping('fromSubSource').
    SubSource refToSource(SubSourceRef ref) => ref.source;
    
    

    And the generate code is

    class ModelMapperImpl extends ModelMapper {
      ModelMapperImpl() : super();
    
      @override
      Target fromSource(Source source) {
        final target = Target();
        return target;
      }
    
      @override
      SubTarget fromSubSource(SubSource subSource) {
        final subtarget = SubTarget();
        return subtarget;
      }
    }
    
    1. Ingore the nullability of type in nested mapping function matching. So the nested mapping function can be matched more easily.

    2. Pass on the mapper pointer to the function mapping method by the named parameters.

    @Mapper
    abstract ModelMapper {
    
        @Mapping(source=funMapping, target="data")
        Target fromSource(Source source);
        // ...
    }
    funMapping(Source source, {
        mapper: ModelMapper
    }) {
    
    }
    
    1. Extend the availability of the shouldAssignList. More list like object can be mapped automatically.
    class Source {
        late Set<String> nameList;
    }
    
    class Target {
        late List<String> nameList;
    }
    
    

    The "Source.nameList" can be mapped to "Target.nameList" automatically.

    1. Add null check code in the generate code. When the input of "nestedMappingMethod" is not nullable and the input property is nullable, the 'null check code' will be generated. Just like:
    target = source == null ? null : nestedMappingMethod(source)
    
    
    opened by skykaka 2
  • @ignore annotation to exclude field from mapper

    @ignore annotation to exclude field from mapper

    Thanks for the library. I'm a fan of Mapstruct and it's good to see a similar effort in the dart world.

    One question or feature request. Is it possible to exclude a field from the automatic mapper, something similar to this?

    The less we explicitly name the fields, the easier it becomes to maintain the mappings. This is particularly relevant when using libraries like Equatable that add helper fields we do not want to include in our mappings.

    This would allow scenarios where we could say "map all fields except this one" as opposed to having to explicitly map all the ones we want.

    opened by luissalgadofreire 2
  • Improvement custom field

    Improvement custom field

    Hi, this is a great package thanks for sharing. I did look at the documentation and did not see anything like...

    For example, I have the class

    class UserResponse { final String profile; ... }

    And I have

    enum UserProfile { investor, consumer }
    
    class User {
    final UserProfile profile;
    ...
    }
    

    How can I make the mapper to map with a custom logic for example:

    profile: enumFromString<UserProfile>(
        UserProfile.values,
        response.profile,
        UserProfile.consumer,
      )
    

    for the profile field. This is just one example there is several more like

    class UserResponse {
       final String zipCode;
       final String number;
       final String street;
    ...
    }
    

    Map to

    class User {
       final Address address;
    }
    
    opened by Rodrigolmti 2
  • Feat/optional static mapping

    Feat/optional static mapping

    This PR adds new parameter to Mapper() annotation to optionally turn off "Static mapping".

    In our project, we usually have "statci helper methods" to map the inner fields of complex objects, but such methods are only for mapping. Without this feature, for each static method a new static $_method is generated which often is not needed and moreover, sometimes it leads to compile errors.

    Breaking change: NO - as this parameter is by default set to true

    opened by petrnymsa 0
  • add the fill data  straightly way

    add the fill data straightly way

    When I use this plugin , I can't fill the data straightly into the target object . I add the isStraight into @Mapping annotation and change the _targetToSource method . I just change the implement way , not to change the content. So it has created the all previous example successfully after my test.

    It makes we can straightly fill the data into the target as the field , we don't need to package the parameter as the function or bean when we are in the easy situation. The example is the the dog_example folder.

    opened by SkyeVaccu 0
  • how to use other mapper as nested mapper

    how to use other mapper as nested mapper

    Hi and thank you for work I have to use other mapper inside my mapper for mapping nested element of my model to entity But currently I have to copy same method from other mapper

    opened by alimcomp 1
  • Map objectId to object

    Map objectId to object

    Question: How to realize this kind of mapping?

    class Target {
      final String text1;
      final Sourse2 source2;
    
      Target(this.text1, this.source2);
    }
    
    class Source {
      final String text1;
      final int source2Id;  
      Source(this.text1);
    }
    
    class Source2 {
      final int id;
      final String text2;
      Source2(this.text2);
    }
    

    "Multiple sources" example is close to that, but not exactly.

    opened by andrew-tpz 4
  • feat: generate extension methods

    feat: generate extension methods

    Description

    Given the following classes:

    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);
    }
    

    And given the following mapper interface:

    // dogmapper.dart
    
    part 'dogmapper.mapper.g.dart';
    
    @Mapper()
    abstract class DogMapper {
      Dog asEntity(DogModel model);
    }
    

    Instead of generating a mapper class, it would be great to generate mapper extension methods as follows:

    // dogmapper.mapper.g.dart
    
    extension ConvertibleDogModel on DogModel {
      Dog get asEntity => Dog(breed, age, name);
    }
    
    opened by mrverdant13 2
Releases(release/1.3.0)
  • release/1.3.0(Oct 7, 2022)

    What's Changed

    • fix: fixes #66 (incompatible with analyzer ^4.0.0) by @luissalgadofreire in https://github.com/smotastic/smartstruct/pull/67
    • Static functional mapping not working by @smotastic in https://github.com/smotastic/smartstruct/pull/69

    New Contributors

    • @luissalgadofreire made their first contribution in https://github.com/smotastic/smartstruct/pull/67

    Full Changelog: https://github.com/smotastic/smartstruct/compare/release/1.2.7...release/1.3.0

    Source code(tar.gz)
    Source code(zip)
  • release/1.2.7(Sep 5, 2022)

    Bugfixes

    • Generator does not recognize inherited methods #56 (Thanks to @skykaka)
    • Unable to generate files in different directories #54 (Thanks to @skykaka)

    Features

    • Static Mapping (#53)
    • Static Mapping with a proxy #59 (Thanks to @skykaka)
    Source code(tar.gz)
    Source code(zip)
  • release/1.2.6(Jan 28, 2022)

  • release/1.2.5(Jan 27, 2022)

Owner
Nils
Passionate, maybe a bit arrogant, Software Developer who likes to have countless private projects which will probably never be finished
Nils
Provides simple conversion between Dart classes and Protobuf / Fixnum classes used in gRPC.

grpc_protobuf_convert Provides simple conversion between Dart classes and Protobuf / Fixnum classes used in gRPC. Using the library Add the repo to yo

null 2 Nov 1, 2022
A simple, unofficial AWS Polly client in dart. Supports generating a URL given an input text and voice identifier.

Flutter AWS Polly Plugin This plugin is a Flutter wrapper for AWS Polly which is a cloud service that converts text into lifelike speech. Getting Star

null 4 Aug 20, 2022
VS Code `.code-workspace` file generator

VS Code .code-workspace file generator (for monorepositories with Dart and Flutter projects) TL;DR; Create yaml file config.yaml (check #Format sectio

Mike T 1 Feb 18, 2022
Simple library for generating random ascii strings

random_string Simple library for generating random ascii strings. Design goals and limitations While this package provides randomBetween for convenien

Damon Douglas 24 Nov 19, 2022
🚀The Flutter dart code generator from zeplin. ex) Container, Text, Color, TextStyle, ... - Save your time.

Flutter Gen Zeplin Extension ?? The Flutter dart code generator from zeplin. ex) Container, Text, Color, TextStyle, ... - Save your time. ⬇ 1.1k Getti

NAVER 49 Oct 12, 2022
The Dart code generator for your package versions. 🎯

The Dart code generator for your package versions. There is no way to get the package version from the code in the Dart ecosystem. Installation Add bu

Daichi Furiya 12 Dec 14, 2022
A CLI tool to help generate dart classes from json returned from API

Json 2 Dart Command line utility Important note There is already a package called json2dart so this package will be called json2dartc ! This project w

Adib Mohsin 38 Oct 5, 2022
Automatically generate usecase classes from your repository class definition in Dart and Flutter

Repo Case Automatically generate usecase classes from your repository class definition in Dart and Flutter. Check out the official guide on repo_case

Sandro Maglione 5 Jul 30, 2022
Contains utility functions and classes in the style of dart:collection to make working with collections easier

The collection package for Dart contains a number of separate libraries with utility functions and classes that makes working with collections easier.

Dart 273 Dec 27, 2022
Starter project and code generator for Flutter/Redux

Flutter Redux Starter/Code Generator Videos Short video ~ 1 minute Long video ~ 10 minutes We're using this approach to develop the Flutter app for In

Hillel Coren 278 Dec 12, 2022
The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.

The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs. Inspired by SwiftGen. Motivation Using asset path str

FlutterGen 1.1k Jan 6, 2023
Swagger/OpenAPI code generator based on Chopper and JsonAnnotation for Flutter

Code partially generated with chopper ?? Build dart types from Swagger/OpenAPI schemas SwaggerDartCodeGenerator is a code generator that looks for *.s

null 187 Jan 5, 2023
A flutter package with classes to help testing applications using the canvas

Canvas test helpers MockCanvas is a utility class for writing tests for canvas operations. It supports the same API as the regular Canvas class from d

Blue Fire 12 Jan 31, 2022
Environment specific config generator for Dart and Flutter applications during CI/CD builds

Environment Config Generator Environment specific config generator. Allows to specify env configuration during CI/CD build. Primarily created to simpl

Denis Beketsky 86 Dec 2, 2022
OpenAPI generator for Dart & Flutter

Fantom Fantom is a cli tool for generating API layer based on OpenAPI Spec. Usage Install fantom $ dart pub global activate fantom Generate API client

6thSolution 13 Oct 18, 2022
OpenAPI generator for Dart & Flutter

Fantom Fantom is a cli tool for generating API layer based on OpenAPI Spec. Usage Install fantom $ dart pub global activate fantom Generate API client

REKAB 13 Oct 18, 2022
Dart R-file generator for build_runner

r_resources This package is made for R-file code generation using build_runner.

Ivanov Nikita 0 Dec 17, 2021
Arissounddart - a Command-line SoundSprite generator for Dart

SoundDart SoundDart is a Command-line SoundSprite generator for Dart. It require

Behruz Hurramov 1 Jan 9, 2022
Destiny is a new, mock-data generator for Dart/Flutter

Destiny is a new, mock-data generator for Dart/Flutter. It uses static methods couched in a destiny namespace as the API.

Aditya Kishore 11 Sep 16, 2022