├── .gitignore ├── smartstruct ├── lib │ ├── smartstruct.dart │ └── src │ │ └── annotations.dart ├── .gitignore ├── pubspec.yaml ├── pubspec.lock ├── analysis_options.yaml ├── LICENSE ├── CHANGELOG.md ├── example │ └── example.md └── README.md ├── example ├── README.md ├── .gitignore ├── lib │ ├── function_mapping │ │ ├── function_utils.dart │ │ ├── function_mapping.mapper.g.dart │ │ ├── nested_mapping_after_function_mapping.mapper.g.dart │ │ ├── function_mapping_with_mapper.mapper.g.dart │ │ ├── function_mapping_more.mapper.g.dart │ │ ├── function_mapping.dart │ │ ├── nested_mapping_after_function_mapping.dart │ │ ├── function_mapping_with_mapper.dart │ │ └── function_mapping_more.dart │ ├── injection │ │ ├── service_locator.dart │ │ ├── injectable_mapper.mapper.g.dart │ │ ├── injectable_mapper.dart │ │ └── service_locator.config.dart │ ├── case_sensitive │ │ ├── case_sensitive.dart │ │ └── case_sensitive.mapper.g.dart │ ├── simple │ │ ├── simple.dart │ │ └── simple.mapper.g.dart │ ├── inheritance │ │ ├── inheritance_35.mapper.g.dart │ │ ├── inheritance_35.dart │ │ ├── inheritance.mapper.g.dart │ │ └── inheritance.dart │ ├── multi_constructor │ │ ├── multi_constructor.dart │ │ └── multi_constructor.mapper.g.dart │ ├── ignore │ │ ├── ignore.dart │ │ └── ignore.mapper.g.dart │ ├── constructor │ ├── static_mapping │ │ ├── static_proxy.dart │ │ ├── static_mapping.dart │ │ ├── static_mapping.mapper.g.dart │ │ └── static_proxy.mapper.g.dart │ ├── field_mapping │ │ ├── field_mapping.mapper.g.dart │ │ └── field_mapping.dart │ ├── nested │ │ ├── nested_mapping.mapper.g.dart │ │ ├── nested.mapper.g.dart │ │ ├── nested_generic_mapping.dart │ │ ├── nested_generic_mapping.mapper.g.dart │ │ ├── nested.dart │ │ ├── nested_mapping.dart │ │ ├── nested_nullable_mapping.mapper.g.dart │ │ └── nested_nullable_mapping.dart │ ├── multiple_sources │ │ ├── multiple_sources.mapper.g.dart │ │ └── multiple_sources.dart │ ├── list │ │ ├── list.dart │ │ ├── list.mapper.g.dart │ │ ├── nullable_list.dart │ │ ├── nullable_list.mapper.g.dart │ │ ├── list_more.mapper.g.dart │ │ └── list_more.dart │ ├── freezed │ │ ├── freezed_classes.dart │ │ ├── freezed_mapper.dart │ │ └── freezed_mapper.mapper.g.dart │ ├── mapper_inheritance │ │ ├── mapper_inheritance.mapper.g.dart │ │ └── mapper_inheritance.dart │ └── complete │ │ ├── complete.mapper.g.dart │ │ └── complete.dart ├── otherlib │ ├── example │ │ └── example.dart │ └── gen │ │ └── example │ │ └── example.mapper.g.dart ├── build.yaml ├── pubspec.yaml └── pubspec.lock ├── generator ├── .gitignore ├── README.md ├── lib │ ├── integrations │ │ └── builder.dart │ ├── code_builders │ │ ├── parameter_copy.dart │ │ ├── static_proxy_builder.dart │ │ ├── class_builder.dart │ │ ├── assignment_builder.dart │ │ └── method_builder.dart │ ├── models │ │ ├── NestMapping.dart │ │ ├── source_assignment.dart │ │ └── RefChain.dart │ ├── generators │ │ └── mapper_generator.dart │ └── mapper_config.dart ├── analysis_options.yaml ├── build.yaml ├── pubspec.yaml ├── test │ ├── src │ │ ├── injectable_mapper_input.dart │ │ ├── simple_mapper_input.dart │ │ ├── constructor_mapper_input.dart │ │ ├── ignore_field_mapper_input.dart │ │ ├── multiple_constructor_input.dart │ │ ├── multiple_source_mapper_input.dart │ │ ├── explicit_field_mapper_input.dart │ │ ├── list_mapper_input.dart │ │ ├── static_proxy_input.dart │ │ ├── nested_mapper_input.dart │ │ ├── nullable_list_mapper_input.dart │ │ ├── mapper_inheritance_input.dart │ │ ├── function_mapper_input.dart │ │ ├── inheritance_mapper_input.dart │ │ ├── nested_mapper_in_mapping_input.dart │ │ ├── nested_generic_mapper_input.dart │ │ ├── mapper_test_input.dart │ │ ├── nullable_nested_mapper_input.dart │ │ ├── case_sensitive_mapper_input.dart │ │ ├── pass_on_mapper_function_mapper_input.dart │ │ ├── nested_mapping_after_function_mapping_input.dart │ │ ├── static_mapper_input.dart │ │ └── collection_mapper_input.dart │ └── mapper_test.dart ├── LICENSE ├── CHANGELOG.md └── pubspec.lock ├── .github └── workflows │ └── github-actions-dart.yml ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | release.sh 2 | 3 | examplemap 4 | 5 | .idea 6 | .vscode -------------------------------------------------------------------------------- /smartstruct/lib/smartstruct.dart: -------------------------------------------------------------------------------- 1 | library smartstruct; 2 | 3 | export 'src/annotations.dart'; 4 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ```console 2 | $ dart pub get 3 | ... 4 | $ dart run build_runner build 5 | ``` 6 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /generator/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /smartstruct/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /generator/README.md: -------------------------------------------------------------------------------- 1 | # Smartstruct - Dart bean mappings - the easy nullsafe way! 2 | 3 | The generator for the [smartstruct](https://pub.dev/packages/smartstruct) library. 4 | -------------------------------------------------------------------------------- /example/lib/function_mapping/function_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct_example/function_mapping/function_mapping.dart'; 2 | 3 | class FunctionUtils { 4 | static int mapAge(DogModel model) => 1; 5 | } 6 | -------------------------------------------------------------------------------- /smartstruct/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: smartstruct 2 | description: smartstruct - A dart bean mapper annotation processor - the easy nullsafe way! 3 | version: 1.4.0 4 | homepage: https://github.com/smotastic/smartstruct 5 | environment: 6 | sdk: ">=2.12.0 <3.0.0" 7 | 8 | dev_dependencies: 9 | lints: ^1.0.1 10 | dependencies: null 11 | -------------------------------------------------------------------------------- /smartstruct/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | lints: 5 | dependency: "direct dev" 6 | description: 7 | name: lints 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.1" 11 | sdks: 12 | dart: ">=2.12.0 <3.0.0" 13 | -------------------------------------------------------------------------------- /generator/lib/integrations/builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:build/build.dart'; 2 | import 'package:source_gen/source_gen.dart'; 3 | import '../generators/mapper_generator.dart'; 4 | 5 | /// Main Builder for the [Mapping] Annotation 6 | Builder smartstructBuilder(BuilderOptions options) => 7 | PartBuilder([MapperGenerator()], '.mapper.g.dart', options: options); 8 | -------------------------------------------------------------------------------- /smartstruct/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 4 | 5 | # Uncomment to specify additional rules. 6 | # linter: 7 | # rules: 8 | # - camel_case_types 9 | 10 | # analyzer: 11 | # exclude: 12 | # - path/to/excluded/files/** 13 | -------------------------------------------------------------------------------- /generator/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 4 | 5 | # Uncomment to specify additional rules. 6 | # linter: 7 | # rules: 8 | # - camel_case_types 9 | 10 | # analyzer: 11 | # exclude: 12 | # - path/to/excluded/files/** 13 | analyzer: 14 | enable-experiment: 15 | - non-nullable 16 | -------------------------------------------------------------------------------- /.github/workflows/github-actions-dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart 2 | 3 | on: [push, pull_request] 4 | defaults: 5 | run: 6 | working-directory: generator 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: dart-lang/setup-dart@v1 14 | 15 | - name: Install dependencies 16 | run: dart pub get 17 | 18 | - name: Run tests 19 | run: dart test -------------------------------------------------------------------------------- /example/otherlib/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | part '../gen/example/example.mapper.g.dart'; 3 | 4 | class Source { 5 | final String userName; 6 | 7 | Source(this.userName); 8 | } 9 | 10 | class Target { 11 | final String username; 12 | 13 | Target({required this.username}); 14 | } 15 | 16 | @Mapper() 17 | abstract class ExampleMapper { 18 | Target fromSource(Source source); 19 | } 20 | -------------------------------------------------------------------------------- /example/lib/injection/service_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | import 'service_locator.config.dart'; 4 | 5 | final getIt = GetIt.instance; 6 | 7 | @InjectableInit( 8 | initializerName: r'$initGetIt', // default 9 | preferRelativeImports: true, // default 10 | asExtension: false, // default 11 | ) 12 | Future configureDependencies() async => $initGetIt(getIt); 13 | -------------------------------------------------------------------------------- /generator/build.yaml: -------------------------------------------------------------------------------- 1 | builders: 2 | mapper: 3 | import: "package:smartstruct_generator/integrations/builder.dart" 4 | builder_factories: ["smartstructBuilder"] 5 | build_extensions: { ".dart": ["mapper.g.dart"] } 6 | auto_apply: dependents 7 | build_to: source 8 | # applies_builders: ["source_gen|combining_builder"] 9 | runs_before: ["injectable_generator|injectable_builder"] 10 | required_inputs: [".g.dart"] 11 | -------------------------------------------------------------------------------- /example/lib/case_sensitive/case_sensitive.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | part 'case_sensitive.mapper.g.dart'; 3 | 4 | class Source { 5 | final String userName; 6 | 7 | Source(this.userName); 8 | } 9 | 10 | class Target { 11 | final String username; 12 | 13 | Target({required this.username}); 14 | } 15 | 16 | @Mapper(caseSensitiveFields: false) 17 | abstract class ExampleMapper { 18 | Target fromSource(Source source); 19 | } 20 | -------------------------------------------------------------------------------- /example/lib/simple/simple.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'simple.mapper.g.dart'; 4 | 5 | class Foo { 6 | final String fooBar; 7 | 8 | Foo(this.fooBar); 9 | } 10 | 11 | class Bar { 12 | final String fooBar; 13 | 14 | Bar(this.fooBar); 15 | } 16 | 17 | /// Mapper showcasing a simple mapping between two fields 18 | @Mapper() 19 | abstract class FooBarMapper { 20 | Bar? fromFoo(Foo? foo); 21 | Foo fromBar(Bar bar); 22 | } 23 | -------------------------------------------------------------------------------- /example/build.yaml: -------------------------------------------------------------------------------- 1 | # Use the different 'build_extensions' on the folder 'otherlib'. So that the code generated by 2 | # smartstruct will be writed to the customized folder ('otherlib/gen'). 3 | 4 | targets: 5 | $default: 6 | sources: 7 | exclude: 8 | - otherlib/** 9 | 10 | $my-target: 11 | sources: 12 | - otherlib/** 13 | 14 | builders: 15 | smartstruct_generator|mapper: 16 | 17 | options: 18 | build_extensions: {"^otherlib/{{}}.dart": "otherlib/gen/{{}}.mapper.g.dart"} 19 | -------------------------------------------------------------------------------- /example/lib/inheritance/inheritance_35.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'inheritance_35.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class DataMapperImpl extends DataMapper { 10 | DataMapperImpl() : super(); 11 | 12 | @override 13 | MyContract fromTarget(MyEntity entity) { 14 | final mycontract = MyContract(entity.id); 15 | return mycontract; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/multi_constructor/multi_constructor.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | part 'multi_constructor.mapper.g.dart'; 3 | 4 | class MultiConTarget { 5 | String? text; 6 | num? number; 7 | 8 | MultiConTarget.single(); 9 | MultiConTarget.multi(this.text, this.number); 10 | } 11 | 12 | class MultiConSource { 13 | final String text; 14 | final num number; 15 | 16 | MultiConSource(this.text, this.number); 17 | } 18 | 19 | @Mapper() 20 | abstract class MultiConMapper { 21 | MultiConTarget fromSource(MultiConSource source); 22 | } 23 | -------------------------------------------------------------------------------- /example/lib/ignore/ignore.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | part 'ignore.mapper.g.dart'; 3 | 4 | class IgnoreSource { 5 | final String ignoreMe; 6 | final String doNotIgnoreMe; 7 | 8 | IgnoreSource(this.ignoreMe, this.doNotIgnoreMe); 9 | } 10 | 11 | class IgnoreTarget { 12 | String? ignoreMe; 13 | final String doNotIgnoreMe; 14 | 15 | IgnoreTarget(this.doNotIgnoreMe); 16 | } 17 | 18 | @Mapper() 19 | abstract class IgnoreMapper { 20 | @Mapping(target: 'ignoreMe', ignore: true) 21 | IgnoreTarget fromIgnore(IgnoreSource source); 22 | } 23 | -------------------------------------------------------------------------------- /example/lib/case_sensitive/case_sensitive.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'case_sensitive.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class ExampleMapperImpl extends ExampleMapper { 10 | ExampleMapperImpl() : super(); 11 | 12 | @override 13 | Target fromSource(Source source) { 14 | final target = Target(username: source.userName); 15 | return target; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/ignore/ignore.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'ignore.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class IgnoreMapperImpl extends IgnoreMapper { 10 | IgnoreMapperImpl() : super(); 11 | 12 | @override 13 | IgnoreTarget fromIgnore(IgnoreSource source) { 14 | final ignoretarget = IgnoreTarget(source.doNotIgnoreMe); 15 | return ignoretarget; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/otherlib/gen/example/example.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of '../../example/example.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class ExampleMapperImpl extends ExampleMapper { 10 | ExampleMapperImpl() : super(); 11 | 12 | @override 13 | Target fromSource(Source source) { 14 | final target = Target(username: source.userName); 15 | return target; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: smartstruct_example 2 | description: example 3 | version: 1.0.1 4 | # homepage: https://www.example.com 5 | environment: 6 | sdk: ">=2.12.0 <3.0.0" 7 | 8 | publish_to: none 9 | dependencies: 10 | freezed_annotation: ^2.1.0 11 | injectable: ^1.5.3 12 | smartstruct: ^1.3.0 13 | dev_dependencies: 14 | build_runner: ^2.1.7 15 | freezed: ^2.1.0+1 16 | injectable_generator: ^1.5.3 17 | smartstruct_generator: ^1.3.0 18 | #smartstruct_generator: 19 | # path: ../generator 20 | dependency_overrides: 21 | smartstruct: 22 | path: ../smartstruct 23 | -------------------------------------------------------------------------------- /example/lib/constructor/constructor.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'constructor.mapper.g.dart'; 4 | 5 | class Source { 6 | final String text; 7 | 8 | Source(this.text); 9 | } 10 | 11 | class Target { 12 | final String text; 13 | 14 | Target(this.text); 15 | } 16 | 17 | @Mapper() 18 | abstract class ConstructorMapper { 19 | ConstructorMapper(String? optionalPos, String requiredPos, 20 | {required String requiredNamed, String? optionalNamed}); 21 | ConstructorMapper.foo(String text); 22 | 23 | Target fromSource(Source source); 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/static_mapping/static_proxy.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:smartstruct/smartstruct.dart'; 3 | 4 | part 'static_proxy.mapper.g.dart'; 5 | 6 | class StaticProxyTarget { 7 | final String text; 8 | final num number; 9 | 10 | StaticProxyTarget(this.text, this.number); 11 | } 12 | 13 | class StaticMappingSource { 14 | final String text; 15 | final num number; 16 | 17 | StaticMappingSource(this.text, this.number); 18 | } 19 | 20 | @Mapper(generateStaticProxy: true) 21 | abstract class StaticMappingMapper { 22 | StaticProxyTarget fromSourceNormal(StaticMappingSource source); 23 | } -------------------------------------------------------------------------------- /example/lib/inheritance/inheritance_35.dart: -------------------------------------------------------------------------------- 1 | // For issue https://github.com/smotastic/smartstruct/issues/35 2 | 3 | import 'package:smartstruct/smartstruct.dart'; 4 | 5 | part 'inheritance_35.mapper.g.dart'; 6 | 7 | abstract class DataContract { 8 | String get id; 9 | } 10 | 11 | class MyContract implements DataContract { 12 | MyContract(this.id); 13 | 14 | @override 15 | final String id; 16 | } 17 | 18 | class MyEntity { 19 | MyEntity(this.id); 20 | 21 | final String id; 22 | } 23 | 24 | @Mapper() 25 | abstract class DataMapper { 26 | MyContract fromTarget(MyEntity entity); 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/field_mapping/field_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'field_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class DogMapperImpl extends DogMapper { 10 | DogMapperImpl() : super(); 11 | 12 | @override 13 | Dog fromDogModel(DogModel model) { 14 | final dog = Dog( 15 | model.dogName, model.breed, model.dogAge, DogMapper._mapDogType(model)); 16 | return dog; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/function_mapping/function_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'function_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class DogMapperImpl extends DogMapper { 10 | DogMapperImpl() : super(); 11 | 12 | @override 13 | Dog fromDogModel(DogModel model) { 14 | final dog = Dog(fullNameWithAge(model), DogMapper.breedCustom(model), 15 | FunctionUtils.mapAge(model)); 16 | return dog; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/nested/nested_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'nested_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class UserMapperImpl extends UserMapper { 10 | UserMapperImpl() : super(); 11 | 12 | @override 13 | User fromResponse(UserResponse response) { 14 | final user = User(response.username, response.address.zipcode, 15 | response.address.street.streetName); 16 | return user; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/multi_constructor/multi_constructor.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'multi_constructor.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class MultiConMapperImpl extends MultiConMapper { 10 | MultiConMapperImpl() : super(); 11 | 12 | @override 13 | MultiConTarget fromSource(MultiConSource source) { 14 | final multicontarget = MultiConTarget.multi(source.text, source.number); 15 | return multicontarget; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/inheritance/inheritance.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'inheritance.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class InheritanceMapperImpl extends InheritanceMapper { 10 | InheritanceMapperImpl() : super(); 11 | 12 | @override 13 | BarTarget fromFoo(FooSource source) { 14 | final bartarget = BarTarget( 15 | source.subFoo, source.superFoo, source.mappable, source.superProp); 16 | return bartarget; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: smartstruct_generator 2 | description: smartstruct - A dart bean mapper annotation processor - the easy nullsafe way! 3 | version: 1.4.0 4 | homepage: https://github.com/smotastic/smartstruct 5 | environment: 6 | sdk: ">=2.12.0 <3.0.0" 7 | 8 | dev_dependencies: 9 | lints: ^1.0.1 10 | source_gen_test: ^1.0.2 11 | test: ^1.22.2 12 | dependencies: 13 | analyzer: ">=5.0.0 <6.0.0" 14 | build: ^2.3.1 15 | code_builder: ^4.4.0 16 | path: ^1.8.1 17 | #smartstruct: 18 | # path: ../smartstruct 19 | smartstruct: ^1.4.0 20 | source_gen: ^1.2.1 21 | #dependency_overrides: 22 | # smartstruct: 23 | # path: ../smartstruct 24 | -------------------------------------------------------------------------------- /example/lib/static_mapping/static_mapping.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'static_mapping.mapper.g.dart'; 4 | 5 | class StaticMappingTarget { 6 | final String text; 7 | final num number; 8 | 9 | StaticMappingTarget(this.text, this.number); 10 | } 11 | 12 | class StaticMappingSource { 13 | final String text; 14 | final num number; 15 | 16 | StaticMappingSource(this.text, this.number); 17 | } 18 | 19 | @Mapper() 20 | abstract class StaticMappingMapper { 21 | StaticMappingTarget fromSourceNormal(StaticMappingSource source); 22 | 23 | static StaticMappingTarget fromSourceStatic(StaticMappingSource source) => 24 | _$fromSourceStatic(source); 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/multiple_sources/multiple_sources.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'multiple_sources.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class MultipleSourcesMapperImpl extends MultipleSourcesMapper { 10 | MultipleSourcesMapperImpl() : super(); 11 | 12 | @override 13 | Target fromSource(Source source, Source2 source2, Source3 source3) { 14 | final target = Target(source.text1, source2.text2, source3.text3, 15 | allText(source, source2, source3)); 16 | return target; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/simple/simple.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'simple.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class FooBarMapperImpl extends FooBarMapper { 10 | FooBarMapperImpl() : super(); 11 | 12 | @override 13 | Bar? fromFoo(Foo? foo) { 14 | if (foo == null) { 15 | return null; 16 | } 17 | ; 18 | final bar = Bar(foo.fooBar); 19 | return bar; 20 | } 21 | 22 | @override 23 | Foo fromBar(Bar bar) { 24 | final foo = Foo(bar.fooBar); 25 | return foo; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/nested/nested.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'nested.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class NestedMapperImpl extends NestedMapper { 10 | NestedMapperImpl() : super(); 11 | 12 | @override 13 | NestedTarget fromModel(NestedSource model) { 14 | final nestedtarget = NestedTarget(fromSubClassModel(model.subNestedSource)); 15 | return nestedtarget; 16 | } 17 | 18 | @override 19 | SubNestedTarget fromSubClassModel(SubNestedSource model) { 20 | final subnestedtarget = SubNestedTarget(model.myProperty); 21 | return subnestedtarget; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/lib/list/list.dart: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import 'package:smartstruct/smartstruct.dart'; 3 | 4 | part 'list.mapper.g.dart'; 5 | 6 | class Source { 7 | final List intList; 8 | final List entryList; 9 | 10 | Source(this.intList, this.entryList); 11 | } 12 | 13 | class SourceEntry { 14 | final String prop; 15 | 16 | SourceEntry(this.prop); 17 | } 18 | 19 | // TARGET 20 | class Target { 21 | final List intList; 22 | final List entryList; 23 | 24 | Target(this.intList, this.entryList); 25 | } 26 | 27 | class TargetEntry { 28 | final String prop; 29 | 30 | TargetEntry(this.prop); 31 | } 32 | 33 | @Mapper() 34 | abstract class ListMapper { 35 | Target fromSource(Source source); 36 | TargetEntry fromSourceEntry(SourceEntry source); 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/list/list.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'list.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class ListMapperImpl extends ListMapper { 10 | ListMapperImpl() : super(); 11 | 12 | @override 13 | Target fromSource(Source source) { 14 | final target = Target(source.intList.map((e) => e).toList(), 15 | source.entryList.map((x) => fromSourceEntry(x)).toList()); 16 | return target; 17 | } 18 | 19 | @override 20 | TargetEntry fromSourceEntry(SourceEntry source) { 21 | final targetentry = TargetEntry(source.prop); 22 | return targetentry; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/freezed/freezed_classes.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'freezed_classes.freezed.dart'; 3 | 4 | @freezed 5 | class FreezedTarget with _$FreezedTarget { 6 | FreezedTarget._(); 7 | factory FreezedTarget(String name, int age) = _FreezedTarget; 8 | } 9 | 10 | @freezed 11 | class FreezedSource with _$FreezedSource { 12 | factory FreezedSource(String name, int age) = _FreezedSource; 13 | } 14 | 15 | @freezed 16 | class FreezedNamedTarget with _$FreezedNamedTarget { 17 | FreezedNamedTarget._(); 18 | factory FreezedNamedTarget({String? name, int? age}) = _FreezedNamedTarget; 19 | } 20 | 21 | @freezed 22 | class FreezedNamedSource with _$FreezedNamedSource { 23 | factory FreezedNamedSource({String? name, int? age}) = _FreezedNamedSource; 24 | } 25 | -------------------------------------------------------------------------------- /generator/test/src/injectable_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class InjectableSource { 4 | final String text; 5 | 6 | InjectableSource(this.text); 7 | } 8 | 9 | class InjectableTarget { 10 | final String text; 11 | 12 | InjectableTarget(this.text); 13 | } 14 | 15 | @Mapper(useInjection: true) 16 | @ShouldGenerate(r''' 17 | @LazySingleton(as: InjectableMapper) 18 | class InjectableMapperImpl extends InjectableMapper { 19 | InjectableMapperImpl() : super(); 20 | 21 | @override 22 | InjectableTarget fromSource(InjectableSource source) { 23 | final injectabletarget = InjectableTarget(source.text); 24 | return injectabletarget; 25 | } 26 | } 27 | ''') 28 | abstract class InjectableMapper { 29 | InjectableTarget fromSource(InjectableSource source); 30 | } 31 | -------------------------------------------------------------------------------- /generator/lib/code_builders/parameter_copy.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:code_builder/code_builder.dart'; 3 | import 'package:source_gen/source_gen.dart'; 4 | 5 | Parameter copyParameter(ParameterElement e) { 6 | if (e.type.element == null) { 7 | throw InvalidGenerationSourceError( 8 | '${e.type} is not a valid parameter type', 9 | element: e, 10 | todo: 'Add valid parameter type to mapping parameters'); 11 | } 12 | 13 | return Parameter( 14 | (b) => b 15 | ..required = e.isRequiredNamed 16 | ..named = e.isNamed 17 | ..name = e.name 18 | ..type = refer(e.type.getDisplayString(withNullability: true)) 19 | ..defaultTo = e.hasDefaultValue ? refer(e.defaultValueCode!).code : null, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /generator/test/src/simple_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class SimpleTarget { 4 | final String text; 5 | final num number; 6 | 7 | SimpleTarget(this.text, this.number); 8 | } 9 | 10 | class SimpleSource { 11 | final String text; 12 | final num number; 13 | 14 | SimpleSource(this.text, this.number); 15 | } 16 | 17 | @Mapper() 18 | @ShouldGenerate(r''' 19 | class SimpleMapperImpl extends SimpleMapper { 20 | SimpleMapperImpl() : super(); 21 | 22 | @override 23 | SimpleTarget fromSource(SimpleSource source) { 24 | final simpletarget = SimpleTarget( 25 | source.text, 26 | source.number, 27 | ); 28 | return simpletarget; 29 | } 30 | } 31 | ''') 32 | abstract class SimpleMapper { 33 | SimpleTarget fromSource(SimpleSource source); 34 | } 35 | -------------------------------------------------------------------------------- /example/lib/freezed/freezed_mapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | import 'package:smartstruct_example/freezed/freezed_classes.dart'; 3 | 4 | part 'freezed_mapper.mapper.g.dart'; 5 | 6 | // Factory with positional parameters 7 | @Mapper() 8 | abstract class FreezedMapper { 9 | @Mapping( 10 | target: 'copyWith', 11 | ignore: 12 | true) // drawback as freezed generates a getter for the copyWith, and we have no way of determining that the copyWith shouldn't be mapped without explicitly ignoring it 13 | FreezedTarget fromModel(FreezedSource model); 14 | } 15 | 16 | // Factory with named parameters 17 | @Mapper() 18 | abstract class FreezedNamedMapper { 19 | @Mapping(target: 'copyWith', ignore: true) 20 | FreezedNamedTarget fromModel(FreezedNamedSource model); 21 | } 22 | -------------------------------------------------------------------------------- /generator/test/src/constructor_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | @Mapper() 4 | @ShouldGenerate(r''' 5 | class ConstructorMapperImpl extends ConstructorMapper { 6 | ConstructorMapperImpl( 7 | String? optionalPos, 8 | String requiredPos, { 9 | required String requiredNamed, 10 | String? optionalNamed, 11 | }) : super( 12 | optionalPos, 13 | requiredPos, 14 | requiredNamed: requiredNamed, 15 | optionalNamed: optionalNamed, 16 | ); 17 | 18 | ConstructorMapperImpl.foo(String text) : super.foo(text); 19 | } 20 | ''') 21 | abstract class ConstructorMapper { 22 | ConstructorMapper(String? optionalPos, String requiredPos, 23 | {required String requiredNamed, String? optionalNamed}); 24 | ConstructorMapper.foo(String text); 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/constructor/constructor.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'constructor.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class ConstructorMapperImpl extends ConstructorMapper { 10 | ConstructorMapperImpl(String? optionalPos, String requiredPos, 11 | {required String requiredNamed, String? optionalNamed}) 12 | : super(optionalPos, requiredPos, 13 | requiredNamed: requiredNamed, optionalNamed: optionalNamed); 14 | 15 | ConstructorMapperImpl.foo(String text) : super.foo(text); 16 | 17 | @override 18 | Target fromSource(Source source) { 19 | final target = Target(source.text); 20 | return target; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /generator/test/src/ignore_field_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class IgnoreSource { 4 | final String ignoreMe; 5 | final String doNotIgnoreMe; 6 | 7 | IgnoreSource(this.ignoreMe, this.doNotIgnoreMe); 8 | } 9 | 10 | class IgnoreTarget { 11 | String? ignoreMe; 12 | final String doNotIgnoreMe; 13 | 14 | IgnoreTarget(this.doNotIgnoreMe); 15 | } 16 | 17 | @Mapper() 18 | @ShouldGenerate(r''' 19 | class IgnoreMapperImpl extends IgnoreMapper { 20 | IgnoreMapperImpl() : super(); 21 | 22 | @override 23 | IgnoreTarget fromIgnore(IgnoreSource source) { 24 | final ignoretarget = IgnoreTarget(source.doNotIgnoreMe); 25 | return ignoretarget; 26 | } 27 | } 28 | ''') 29 | abstract class IgnoreMapper { 30 | @Mapping(target: 'ignoreMe', ignore: true) 31 | IgnoreTarget fromIgnore(IgnoreSource source); 32 | } 33 | -------------------------------------------------------------------------------- /example/lib/static_mapping/static_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'static_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class StaticMappingMapperImpl extends StaticMappingMapper { 10 | StaticMappingMapperImpl() : super(); 11 | 12 | @override 13 | StaticMappingTarget fromSourceNormal(StaticMappingSource source) { 14 | final staticmappingtarget = StaticMappingTarget(source.text, source.number); 15 | return staticmappingtarget; 16 | } 17 | } 18 | 19 | StaticMappingTarget _$fromSourceStatic(StaticMappingSource source) { 20 | final staticmappingtarget = StaticMappingTarget(source.text, source.number); 21 | return staticmappingtarget; 22 | } 23 | -------------------------------------------------------------------------------- /example/lib/list/nullable_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'nullable_list.mapper.g.dart'; 4 | 5 | class NullableListSource { 6 | final List? list; 7 | final List? list2; 8 | 9 | NullableListSource(this.list, this.list2); 10 | } 11 | 12 | class ListSubSource { 13 | final String text; 14 | 15 | ListSubSource(this.text); 16 | } 17 | 18 | class NullableListTarget { 19 | final List? list; 20 | final List list2; 21 | 22 | NullableListTarget(this.list, this.list2); 23 | } 24 | 25 | class ListSubTarget { 26 | final String text; 27 | 28 | ListSubTarget(this.text); 29 | } 30 | 31 | @Mapper() 32 | abstract class NullableListMapper { 33 | NullableListTarget fromSource(NullableListSource source); 34 | ListSubTarget fromSubSource(ListSubSource source); 35 | } 36 | -------------------------------------------------------------------------------- /generator/test/src/multiple_constructor_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class MultiConTarget { 4 | String? text; 5 | num? number; 6 | 7 | MultiConTarget.single(); 8 | MultiConTarget.multi(this.text, this.number); 9 | } 10 | 11 | class MultiConSource { 12 | final String text; 13 | final num number; 14 | 15 | MultiConSource(this.text, this.number); 16 | } 17 | 18 | @Mapper() 19 | @ShouldGenerate(r''' 20 | class MultiConMapperImpl extends MultiConMapper { 21 | MultiConMapperImpl() : super(); 22 | 23 | @override 24 | MultiConTarget fromSource(MultiConSource source) { 25 | final multicontarget = MultiConTarget.multi( 26 | source.text, 27 | source.number, 28 | ); 29 | return multicontarget; 30 | } 31 | } 32 | ''') 33 | abstract class MultiConMapper { 34 | MultiConTarget fromSource(MultiConSource source); 35 | } 36 | -------------------------------------------------------------------------------- /example/lib/nested/nested_generic_mapping.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | part 'nested_generic_mapping.mapper.g.dart'; 3 | 4 | 5 | class NestedGeneric { 6 | 7 | T value; 8 | 9 | NestedGeneric(this.value); 10 | } 11 | 12 | class NestedGenericSource { 13 | NestedGeneric data; 14 | 15 | NestedGenericSource(this.data); 16 | } 17 | 18 | 19 | class NestedGenericSubSource {} 20 | 21 | class NestedGenericTarget { 22 | 23 | NestedGenericSubTarget data; 24 | 25 | NestedGenericTarget(this.data); 26 | } 27 | 28 | 29 | class NestedGenericSubTarget { 30 | } 31 | 32 | 33 | @Mapper() 34 | abstract class NestedGenericMapper { 35 | 36 | @Mapping(target: "data", source: "source.data.value") 37 | NestedGenericTarget fromSource(NestedGenericSource source); 38 | 39 | NestedGenericSubTarget fromSubSource(NestedGenericSubSource source); 40 | } -------------------------------------------------------------------------------- /example/lib/nested/nested_generic_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'nested_generic_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class NestedGenericMapperImpl extends NestedGenericMapper { 10 | NestedGenericMapperImpl() : super(); 11 | 12 | @override 13 | NestedGenericTarget fromSource(NestedGenericSource source) { 14 | final nestedgenerictarget = 15 | NestedGenericTarget(fromSubSource(source.data.value)); 16 | return nestedgenerictarget; 17 | } 18 | 19 | @override 20 | NestedGenericSubTarget fromSubSource(NestedGenericSubSource source) { 21 | final nestedgenericsubtarget = NestedGenericSubTarget(); 22 | return nestedgenericsubtarget; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/static_mapping/static_proxy.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'static_proxy.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class StaticMappingMapperImpl extends StaticMappingMapper { 10 | StaticMappingMapperImpl() : super(); 11 | 12 | @override 13 | StaticProxyTarget fromSourceNormal(StaticMappingSource source) { 14 | final staticproxytarget = StaticProxyTarget(source.text, source.number); 15 | return staticproxytarget; 16 | } 17 | } 18 | 19 | class StaticMappingMapper$ { 20 | static final StaticMappingMapper mapper = StaticMappingMapperImpl(); 21 | 22 | static StaticProxyTarget fromSourceNormal(StaticMappingSource source) => 23 | mapper.fromSourceNormal(source); 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/list/nullable_list.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'nullable_list.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class NullableListMapperImpl extends NullableListMapper { 10 | NullableListMapperImpl() : super(); 11 | 12 | @override 13 | NullableListTarget fromSource(NullableListSource source) { 14 | final nullablelisttarget = NullableListTarget( 15 | source.list?.map(fromSubSource).toList(), 16 | source.list2?.map(fromSubSource).toList() ?? []); 17 | return nullablelisttarget; 18 | } 19 | 20 | @override 21 | ListSubTarget fromSubSource(ListSubSource source) { 22 | final listsubtarget = ListSubTarget(source.text); 23 | return listsubtarget; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/function_mapping/nested_mapping_after_function_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'nested_mapping_after_function_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class NestedMappingAfterFunctionMappingMapperImpl 10 | extends NestedMappingAfterFunctionMappingMapper { 11 | NestedMappingAfterFunctionMappingMapperImpl() : super(); 12 | 13 | @override 14 | NestedTarget fromRefSource(NestedRefSource source) { 15 | final nestedtarget = NestedTarget(fromSubSource(mapRef(source))); 16 | return nestedtarget; 17 | } 18 | 19 | @override 20 | NestedSubTarget fromSubSource(NestedSubSource source) { 21 | final nestedsubtarget = NestedSubTarget(); 22 | return nestedsubtarget; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/function_mapping/function_mapping_with_mapper.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'function_mapping_with_mapper.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class PassOnMapperFunctionMapperImpl extends PassOnMapperFunctionMapper { 10 | PassOnMapperFunctionMapperImpl() : super(); 11 | 12 | @override 13 | ComplexFunctionTarget fromSource(ComplexFunctionSource source) { 14 | final complexfunctiontarget = 15 | ComplexFunctionTarget(mapComplexSubSource(source, mapper: this)); 16 | return complexfunctiontarget; 17 | } 18 | 19 | @override 20 | FunctionSubTarget fromSubSource(FunctionSubSource source) { 21 | final functionsubtarget = FunctionSubTarget(); 22 | return functionsubtarget; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/multiple_sources/multiple_sources.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'multiple_sources.mapper.g.dart'; 4 | 5 | class Target { 6 | final String text1; 7 | final String text2; 8 | final String text3; 9 | final String allText; 10 | 11 | Target(this.text1, this.text2, this.text3, this.allText); 12 | } 13 | 14 | class Source { 15 | final String text1; 16 | Source(this.text1); 17 | } 18 | 19 | class Source2 { 20 | final String text2; 21 | Source2(this.text2); 22 | } 23 | 24 | class Source3 { 25 | final String text3; 26 | Source3(this.text3); 27 | } 28 | 29 | @Mapper() 30 | abstract class MultipleSourcesMapper { 31 | @Mapping(source: allText, target: 'allText') 32 | Target fromSource(Source source, Source2 source2, Source3 source3); 33 | } 34 | 35 | String allText(Source source, Source2 source2, Source3 source3) => 36 | source.text1 + source2.text2 + source3.text3; 37 | -------------------------------------------------------------------------------- /example/lib/freezed/freezed_mapper.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'freezed_mapper.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class FreezedMapperImpl extends FreezedMapper { 10 | FreezedMapperImpl() : super(); 11 | 12 | @override 13 | FreezedTarget fromModel(FreezedSource model) { 14 | final freezedtarget = FreezedTarget(model.name, model.age); 15 | return freezedtarget; 16 | } 17 | } 18 | 19 | class FreezedNamedMapperImpl extends FreezedNamedMapper { 20 | FreezedNamedMapperImpl() : super(); 21 | 22 | @override 23 | FreezedNamedTarget fromModel(FreezedNamedSource model) { 24 | final freezednamedtarget = 25 | FreezedNamedTarget(name: model.name, age: model.age); 26 | return freezednamedtarget; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/nested/nested.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'nested.mapper.g.dart'; 4 | 5 | // TARGET 6 | 7 | class NestedTarget { 8 | NestedTarget(this.subNestedTarget); 9 | 10 | final SubNestedTarget subNestedTarget; 11 | } 12 | 13 | class SubNestedTarget { 14 | SubNestedTarget(this.myProperty); 15 | 16 | final String myProperty; 17 | } 18 | 19 | // SOURCE 20 | 21 | class NestedSource { 22 | NestedSource(this.subNestedSource); 23 | 24 | final SubNestedSource subNestedSource; 25 | } 26 | 27 | class SubNestedSource { 28 | SubNestedSource(this.myProperty); 29 | 30 | final String myProperty; 31 | } 32 | 33 | /// Mapper showcasing how to map nested classes 34 | @Mapper() 35 | abstract class NestedMapper { 36 | @Mapping(source: 'subNestedSource', target: 'subNestedTarget') 37 | NestedTarget fromModel(NestedSource model); 38 | 39 | SubNestedTarget fromSubClassModel(SubNestedSource model); 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/function_mapping/function_mapping_more.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'function_mapping_more.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class DogMapperImpl extends DogMapper { 10 | DogMapperImpl() : super(); 11 | 12 | @override 13 | Dog fromDogModel(DogModel model) { 14 | final dog = 15 | Dog(fullNameWithAge(model), DogMapper.breedCustom(model), model.age); 16 | dog.model = () { 17 | final tmp = toAgeHolderSource(model); 18 | return tmp == null ? null : fromAgeHolderSource(tmp); 19 | }(); 20 | return dog; 21 | } 22 | 23 | @override 24 | AgeHolderTarget fromAgeHolderSource(AgeHolderSource model) { 25 | final ageholdertarget = AgeHolderTarget(); 26 | ageholdertarget.age = model.age; 27 | return ageholdertarget; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/lib/nested/nested_mapping.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'nested_mapping.mapper.g.dart'; 4 | 5 | class User { 6 | final String username; 7 | final String zipcode; 8 | final String street; 9 | 10 | User(this.username, this.zipcode, this.street); 11 | } 12 | 13 | class UserResponse { 14 | final String username; 15 | final AddressResponse address; 16 | 17 | UserResponse(this.username, this.address); 18 | } 19 | 20 | class AddressResponse { 21 | final String zipcode; 22 | final StreetResponse street; 23 | 24 | AddressResponse(this.zipcode, this.street); 25 | } 26 | 27 | class StreetResponse { 28 | final num streetNumber; 29 | final String streetName; 30 | 31 | StreetResponse(this.streetNumber, this.streetName); 32 | } 33 | 34 | @Mapper() 35 | abstract class UserMapper { 36 | @Mapping(target: 'zipcode', source: 'response.address.zipcode') 37 | @Mapping(target: 'street', source: 'response.address.street.streetName') 38 | User fromResponse(UserResponse response); 39 | } 40 | -------------------------------------------------------------------------------- /generator/lib/models/NestMapping.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:analyzer/dart/element/element.dart'; 3 | import 'package:analyzer/dart/element/nullability_suffix.dart'; 4 | 5 | class NestMapping { 6 | 7 | final MethodElement method; 8 | final VariableElement input; 9 | late final bool returnNullable; 10 | 11 | late final bool methodReturnNullable; 12 | late final bool inputNullable; 13 | late final bool methodParamterNullable; 14 | 15 | NestMapping(this.method, this.input) { 16 | methodReturnNullable = method.returnType.nullabilitySuffix == NullabilitySuffix.question; 17 | inputNullable = input.type.nullabilitySuffix == NullabilitySuffix.question; 18 | methodParamterNullable = method.parameters.first.type.nullabilitySuffix == NullabilitySuffix.question; 19 | 20 | returnNullable = 21 | (inputNullable && !methodParamterNullable) || // 'a == null ? null : call(a)'. So the output is nullable. 22 | (inputNullable && methodReturnNullable); // if input is not null, the method return is not 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /example/lib/function_mapping/function_mapping.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | import 'package:smartstruct_example/function_mapping/function_utils.dart'; 3 | part 'function_mapping.mapper.g.dart'; 4 | 5 | // TARGET 6 | 7 | class Dog { 8 | final String name; 9 | final String breed; 10 | final int age; 11 | 12 | Dog(this.name, this.breed, this.age); 13 | } 14 | 15 | // SOURCE 16 | 17 | class DogModel { 18 | final String name; 19 | final int age; 20 | 21 | DogModel(this.name, this.age); 22 | } 23 | 24 | /// Mapper showcasing explicit fieldmapping in case fields do not match their respective fieldnames 25 | @Mapper() 26 | abstract class DogMapper { 27 | static String breedCustom(DogModel model) => 'customBreed'; 28 | 29 | @Mapping(source: fullNameWithAge, target: 'name') 30 | @Mapping(source: FunctionUtils.mapAge, target: 'age') 31 | @Mapping(source: breedCustom, target: 'breed') 32 | Dog fromDogModel(DogModel model); 33 | } 34 | 35 | String fullNameWithAge(DogModel model) => model.name + '${model.age}'; 36 | -------------------------------------------------------------------------------- /example/lib/inheritance/inheritance.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'inheritance.mapper.g.dart'; 4 | 5 | abstract class SuperFooSource { 6 | final String superFoo; 7 | String? _superProp; 8 | 9 | String? get superProp => _superProp; 10 | 11 | set superProp(String? superProp) { 12 | _superProp = superProp; 13 | } 14 | 15 | String get superText; 16 | 17 | String get mappable { 18 | return 'mappable'; 19 | } 20 | 21 | SuperFooSource(this.superFoo); 22 | } 23 | 24 | class FooSource extends SuperFooSource { 25 | final String subFoo; 26 | @override 27 | final String superText; 28 | 29 | FooSource(this.subFoo, this.superText, String superFoo) : super(superFoo); 30 | } 31 | 32 | class BarTarget { 33 | final String subFoo; 34 | final String superFoo; 35 | final String mappable; 36 | final String? superProp; 37 | 38 | BarTarget(this.subFoo, this.superFoo, this.mappable, this.superProp); 39 | } 40 | 41 | @Mapper() 42 | abstract class InheritanceMapper { 43 | BarTarget fromFoo(FooSource source); 44 | } 45 | -------------------------------------------------------------------------------- /generator/test/src/multiple_source_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class MultiSourceTarget { 4 | final String text; 5 | final num number; 6 | 7 | MultiSourceTarget(this.text, this.number); 8 | } 9 | 10 | class MultiSourceSource { 11 | final String text; 12 | 13 | MultiSourceSource(this.text); 14 | } 15 | 16 | class MultiSourceSourceSecond { 17 | final String number; 18 | 19 | MultiSourceSourceSecond(this.number); 20 | } 21 | 22 | @Mapper() 23 | @ShouldGenerate(r''' 24 | class MultipleSourceMapperImpl extends MultipleSourceMapper { 25 | MultipleSourceMapperImpl() : super(); 26 | 27 | @override 28 | MultiSourceTarget fromSource( 29 | MultiSourceSource source, 30 | MultiSourceSourceSecond source2, 31 | ) { 32 | final multisourcetarget = MultiSourceTarget( 33 | source.text, 34 | source2.number, 35 | ); 36 | return multisourcetarget; 37 | } 38 | } 39 | ''') 40 | abstract class MultipleSourceMapper { 41 | MultiSourceTarget fromSource( 42 | MultiSourceSource source, MultiSourceSourceSecond source2); 43 | } 44 | -------------------------------------------------------------------------------- /example/lib/injection/injectable_mapper.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'injectable_mapper.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | @LazySingleton(as: InjectableMapper) 10 | class InjectableMapperImpl extends InjectableMapper { 11 | InjectableMapperImpl(InjectableNestedMapper _nestedMapper) 12 | : super(_nestedMapper); 13 | 14 | @override 15 | InjectableTarget fromSource(InjectableSource source) { 16 | final injectabletarget = InjectableTarget(fromNestedSource(source.nested)); 17 | return injectabletarget; 18 | } 19 | } 20 | 21 | @LazySingleton(as: InjectableNestedMapper) 22 | class InjectableNestedMapperImpl extends InjectableNestedMapper { 23 | InjectableNestedMapperImpl() : super(); 24 | 25 | @override 26 | InjectableNestedTarget fromSource(InjectableNestedSource source) { 27 | final injectablenestedtarget = InjectableNestedTarget(source.text); 28 | return injectablenestedtarget; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/lib/field_mapping/field_mapping.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'field_mapping.mapper.g.dart'; 4 | 5 | enum DogType { happy, angry } 6 | 7 | // TARGET 8 | 9 | class Dog { 10 | final String name; 11 | final String breed; 12 | final int age; 13 | final DogType dogType; 14 | 15 | Dog(this.name, this.breed, this.age, this.dogType); 16 | } 17 | 18 | // SOURCE 19 | 20 | class DogModel { 21 | final String dogName; 22 | final String breed; 23 | final int dogAge; 24 | final String dogType; 25 | 26 | DogModel(this.dogName, this.breed, this.dogAge, this.dogType); 27 | } 28 | 29 | /// Mapper showcasing explicit fieldmapping in case fields do not match their respective fieldnames 30 | @Mapper() 31 | abstract class DogMapper { 32 | static DogType _mapDogType(DogModel model) { 33 | if (model.dogType == 'angry') return DogType.angry; 34 | 35 | return DogType.happy; 36 | } 37 | 38 | @Mapping(source: 'dogName', target: 'name') 39 | @Mapping(source: 'dogAge', target: 'age') 40 | @Mapping(source: _mapDogType, target: 'dogType') 41 | Dog fromDogModel(DogModel model); 42 | } 43 | -------------------------------------------------------------------------------- /example/lib/nested/nested_nullable_mapping.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'nested_nullable_mapping.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class NullableNestedMapperImpl extends NullableNestedMapper { 10 | NullableNestedMapperImpl() : super(); 11 | 12 | @override 13 | NullableNestedTarget fromSource(NestedSource source) { 14 | final nullablenestedtarget = 15 | NullableNestedTarget(fromSubSource(source.nested)); 16 | return nullablenestedtarget; 17 | } 18 | 19 | @override 20 | NullableNestedTarget fromNullableSource(NullableNestedSource source) { 21 | final nullablenestedtarget = NullableNestedTarget( 22 | source.nested == null ? null : fromSubSource(source.nested!)); 23 | return nullablenestedtarget; 24 | } 25 | 26 | @override 27 | NestedSubTarget fromSubSource(NestedSubSource source) { 28 | final nestedsubtarget = NestedSubTarget(); 29 | return nestedsubtarget; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /generator/test/src/explicit_field_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class ExplicitFieldSource { 4 | final String fieldTextSource; 5 | final num fieldNumSource; 6 | 7 | ExplicitFieldSource(this.fieldTextSource, this.fieldNumSource); 8 | } 9 | 10 | class ExplicitFieldTarget { 11 | final String fieldTextTarget; 12 | final num fieldNumTarget; 13 | 14 | ExplicitFieldTarget(this.fieldTextTarget, this.fieldNumTarget); 15 | } 16 | 17 | @Mapper() 18 | @ShouldGenerate(r''' 19 | class ExplicitFieldMapperImpl extends ExplicitFieldMapper { 20 | ExplicitFieldMapperImpl() : super(); 21 | 22 | @override 23 | ExplicitFieldTarget fromSource(ExplicitFieldSource source) { 24 | final explicitfieldtarget = ExplicitFieldTarget( 25 | source.fieldTextSource, 26 | source.fieldNumSource, 27 | ); 28 | return explicitfieldtarget; 29 | } 30 | } 31 | ''') 32 | abstract class ExplicitFieldMapper { 33 | @Mapping(source: 'fieldTextSource', target: 'fieldTextTarget') 34 | @Mapping(source: 'fieldNumSource', target: 'fieldNumTarget') 35 | ExplicitFieldTarget fromSource(ExplicitFieldSource source); 36 | } 37 | -------------------------------------------------------------------------------- /generator/test/src/list_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class ListSource { 4 | final List list; 5 | 6 | ListSource(this.list); 7 | } 8 | 9 | class ListSubSource { 10 | final String text; 11 | 12 | ListSubSource(this.text); 13 | } 14 | 15 | class ListTarget { 16 | final List list; 17 | 18 | ListTarget(this.list); 19 | } 20 | 21 | class ListSubTarget { 22 | final String text; 23 | 24 | ListSubTarget(this.text); 25 | } 26 | 27 | @Mapper() 28 | @ShouldGenerate(r''' 29 | class ListMapperImpl extends ListMapper { 30 | ListMapperImpl() : super(); 31 | 32 | @override 33 | ListTarget fromSource(ListSource source) { 34 | final listtarget = 35 | ListTarget(source.list.map((x) => fromSubSource(x)).toList()); 36 | return listtarget; 37 | } 38 | 39 | @override 40 | ListSubTarget fromSubSource(ListSubSource source) { 41 | final listsubtarget = ListSubTarget(source.text); 42 | return listsubtarget; 43 | } 44 | } 45 | ''') 46 | abstract class ListMapper { 47 | ListTarget fromSource(ListSource source); 48 | ListSubTarget fromSubSource(ListSubSource source); 49 | } 50 | -------------------------------------------------------------------------------- /generator/test/src/static_proxy_input.dart: -------------------------------------------------------------------------------- 1 | 2 | part of 'mapper_test_input.dart'; 3 | 4 | 5 | class StaticProxyTarget { 6 | final String text; 7 | final num number; 8 | 9 | StaticProxyTarget(this.text, this.number); 10 | } 11 | 12 | class StaticProxySource { 13 | final String text; 14 | final num number; 15 | 16 | StaticProxySource(this.text, this.number); 17 | } 18 | 19 | @Mapper(generateStaticProxy: true) 20 | @ShouldGenerate(''' 21 | class StaticProxyMapperImpl extends StaticProxyMapper { 22 | StaticProxyMapperImpl() : super(); 23 | 24 | @override 25 | StaticProxyTarget fromSourceNormal(StaticMappingSource source) { 26 | final staticproxytarget = StaticProxyTarget( 27 | source.text, 28 | source.number, 29 | ); 30 | return staticproxytarget; 31 | } 32 | } 33 | 34 | class StaticProxyMapper\$ { 35 | static final StaticProxyMapper mapper = StaticProxyMapperImpl(); 36 | 37 | static StaticProxyTarget fromSourceNormal(StaticMappingSource source) => 38 | mapper.fromSourceNormal(source); 39 | } 40 | ''') 41 | abstract class StaticProxyMapper { 42 | StaticProxyTarget fromSourceNormal(StaticMappingSource source); 43 | } 44 | -------------------------------------------------------------------------------- /generator/test/src/nested_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class NestedSource { 4 | final NestedSubSource nested; 5 | 6 | NestedSource(this.nested); 7 | } 8 | 9 | class NestedSubSource { 10 | final String text; 11 | 12 | NestedSubSource(this.text); 13 | } 14 | 15 | class NestedTarget { 16 | final NestedSubTarget nested; 17 | 18 | NestedTarget(this.nested); 19 | } 20 | 21 | class NestedSubTarget { 22 | final String text; 23 | 24 | NestedSubTarget(this.text); 25 | } 26 | 27 | @Mapper() 28 | @ShouldGenerate(r''' 29 | class NestedMapperImpl extends NestedMapper { 30 | NestedMapperImpl() : super(); 31 | 32 | @override 33 | NestedTarget fromSource(NestedSource source) { 34 | final nestedtarget = NestedTarget(fromSubSource(source.nested)); 35 | return nestedtarget; 36 | } 37 | 38 | @override 39 | NestedSubTarget fromSubSource(NestedSubSource source) { 40 | final nestedsubtarget = NestedSubTarget(source.text); 41 | return nestedsubtarget; 42 | } 43 | } 44 | ''') 45 | abstract class NestedMapper { 46 | NestedTarget fromSource(NestedSource source); 47 | NestedSubTarget fromSubSource(NestedSubSource source); 48 | } 49 | -------------------------------------------------------------------------------- /example/lib/injection/injectable_mapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:injectable/injectable.dart'; 2 | import 'package:smartstruct/smartstruct.dart'; 3 | 4 | part 'injectable_mapper.mapper.g.dart'; 5 | 6 | class InjectableSource { 7 | final InjectableNestedSource nested; 8 | 9 | InjectableSource(this.nested); 10 | } 11 | 12 | class InjectableNestedSource { 13 | final String text; 14 | 15 | InjectableNestedSource(this.text); 16 | } 17 | 18 | class InjectableTarget { 19 | final InjectableNestedTarget nested; 20 | 21 | InjectableTarget(this.nested); 22 | } 23 | 24 | class InjectableNestedTarget { 25 | final String text; 26 | 27 | InjectableNestedTarget(this.text); 28 | } 29 | 30 | @Mapper(useInjection: true) 31 | abstract class InjectableMapper { 32 | InjectableNestedMapper _nestedMapper; 33 | InjectableMapper(this._nestedMapper); 34 | 35 | InjectableTarget fromSource(InjectableSource source); 36 | InjectableNestedTarget fromNestedSource(InjectableNestedSource source) { 37 | return _nestedMapper.fromSource(source); 38 | } 39 | } 40 | 41 | @Mapper(useInjection: true) 42 | abstract class InjectableNestedMapper { 43 | InjectableNestedTarget fromSource(InjectableNestedSource source); 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/function_mapping/nested_mapping_after_function_mapping.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:smartstruct/smartstruct.dart'; 3 | part 'nested_mapping_after_function_mapping.mapper.g.dart'; 4 | 5 | class NestedSubSourceRef { 6 | final NestedSubSource _subSource; 7 | 8 | NestedSubSourceRef(this._subSource); 9 | 10 | getSubSource() { 11 | return _subSource; 12 | } 13 | } 14 | 15 | class NestedSubSource { 16 | } 17 | 18 | class NestedRefSource { 19 | NestedSubSourceRef ref; 20 | 21 | NestedRefSource(this.ref); 22 | } 23 | 24 | class NestedSubTarget { 25 | } 26 | 27 | class NestedTarget { 28 | NestedSubTarget nested; 29 | 30 | NestedTarget(this.nested); 31 | } 32 | 33 | @Mapper() 34 | abstract class NestedMappingAfterFunctionMappingMapper { 35 | 36 | // The mapRef return a NestedSubSource and the target field type is 37 | // NestedSubTarget. So the SmartStruct will apply the nested mapping 38 | // "fromSubSource". 39 | @Mapping(target: "nested", source: mapRef) 40 | NestedTarget fromRefSource(NestedRefSource source); 41 | 42 | NestedSubTarget fromSubSource(NestedSubSource source); 43 | 44 | } 45 | 46 | 47 | NestedSubSource mapRef(NestedRefSource source) => source.ref.getSubSource(); -------------------------------------------------------------------------------- /example/lib/mapper_inheritance/mapper_inheritance.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'mapper_inheritance.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class UserLoginContractFromEntityMapperImpl 10 | extends UserLoginContractFromEntityMapper { 11 | UserLoginContractFromEntityMapperImpl() : super(); 12 | 13 | @override 14 | UserLoginContract? fromEntity(UserLoginEntity? entity) { 15 | if (entity == null) { 16 | return null; 17 | } 18 | ; 19 | final userlogincontract = UserLoginContract(entity.age, entity.id); 20 | return userlogincontract; 21 | } 22 | } 23 | 24 | class UserLoginContractFromEntityMapper2Impl 25 | extends UserLoginContractFromEntityMapper2 { 26 | UserLoginContractFromEntityMapper2Impl() : super(); 27 | 28 | @override 29 | UserLoginContract2? fromEntity(UserLoginEntity? entity) { 30 | if (entity == null) { 31 | return null; 32 | } 33 | ; 34 | final userlogincontract2 = UserLoginContract2(entity.age, entity.id); 35 | return userlogincontract2; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/injection/service_locator.config.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // InjectableConfigGenerator 5 | // ************************************************************************** 6 | 7 | // ignore_for_file: no_leading_underscores_for_library_prefixes 8 | import 'package:get_it/get_it.dart' as _i1; 9 | import 'package:injectable/injectable.dart' as _i2; 10 | 11 | import '../complete/complete.dart' as _i3; 12 | import 'injectable_mapper.dart' as _i4; // ignore_for_file: unnecessary_lambdas 13 | 14 | // ignore_for_file: lines_longer_than_80_chars 15 | /// initializes the registration of provided dependencies inside of [GetIt] 16 | _i1.GetIt $initGetIt(_i1.GetIt get, 17 | {String? environment, _i2.EnvironmentFilter? environmentFilter}) { 18 | final gh = _i2.GetItHelper(get, environment, environmentFilter); 19 | gh.lazySingleton<_i3.ExampleMapper>(() => _i3.ExampleMapperImpl()); 20 | gh.lazySingleton<_i4.InjectableNestedMapper>( 21 | () => _i4.InjectableNestedMapperImpl()); 22 | gh.lazySingleton<_i4.InjectableMapper>( 23 | () => _i4.InjectableMapperImpl(get<_i4.InjectableNestedMapper>())); 24 | return get; 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/mapper_inheritance/mapper_inheritance.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | 3 | part 'mapper_inheritance.mapper.g.dart'; 4 | 5 | class DataContract { 6 | 7 | int id; 8 | 9 | DataContract(this.id); 10 | } 11 | 12 | class DomainEntity { 13 | int id; 14 | 15 | DomainEntity(this.id); 16 | 17 | } 18 | 19 | abstract class ContractFromEntityMapper { 20 | C? fromEntity(E? entity); 21 | } 22 | 23 | class UserLoginContract extends DataContract{ 24 | int age; 25 | 26 | UserLoginContract(this.age, int id) : super(id); 27 | } 28 | 29 | class UserLoginContract2 extends UserLoginContract{ 30 | 31 | UserLoginContract2(int age, int id) : super(age, id); 32 | } 33 | 34 | class UserLoginEntity extends DomainEntity{ 35 | int age; 36 | 37 | UserLoginEntity(this.age, int id) : super(id); 38 | } 39 | 40 | @Mapper() 41 | abstract class UserLoginContractFromEntityMapper extends ContractFromEntityMapper { 42 | } 43 | 44 | @Mapper() 45 | abstract class UserLoginContractFromEntityMapper2 extends ContractFromEntityMapper { 46 | @override 47 | UserLoginContract2? fromEntity(UserLoginEntity? entity); 48 | } -------------------------------------------------------------------------------- /generator/test/src/nullable_list_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class NullableListSource { 4 | final List? list; 5 | final List? list2; 6 | 7 | NullableListSource(this.list, this.list2); 8 | } 9 | 10 | class NullableListTarget { 11 | final List? list; 12 | final List list2; 13 | 14 | NullableListTarget(this.list, this.list2); 15 | } 16 | 17 | @Mapper() 18 | @ShouldGenerate(r''' 19 | class NullableListMapperImpl extends NullableListMapper { 20 | NullableListMapperImpl() : super(); 21 | 22 | @override 23 | NullableListTarget fromSource(NullableListSource source) { 24 | final nullablelisttarget = NullableListTarget( 25 | source.list?.map((x) => fromSubSource(x)).toList(), 26 | source.list2?.map((x) => fromSubSource(x)).toList() ?? [], 27 | ); 28 | return nullablelisttarget; 29 | } 30 | 31 | @override 32 | ListSubTarget fromSubSource(ListSubSource source) { 33 | final listsubtarget = ListSubTarget(source.text); 34 | return listsubtarget; 35 | } 36 | } 37 | ''') 38 | abstract class NullableListMapper { 39 | NullableListTarget fromSource(NullableListSource source); 40 | ListSubTarget fromSubSource(ListSubSource source); 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/function_mapping/function_mapping_with_mapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | part 'function_mapping_with_mapper.mapper.g.dart'; 3 | 4 | class FunctionSubSource { 5 | } 6 | 7 | class FunctionSubTarget { 8 | } 9 | 10 | class ComplexFunctionSubTargetHolder { 11 | 12 | FunctionSubTarget? _data; 13 | 14 | setData(FunctionSubTarget data) { 15 | _data = data; 16 | } 17 | } 18 | 19 | class ComplexFunctionSource { 20 | FunctionSubSource subSource; 21 | 22 | ComplexFunctionSource(this.subSource); 23 | } 24 | 25 | class ComplexFunctionTarget { 26 | ComplexFunctionSubTargetHolder holder; 27 | 28 | ComplexFunctionTarget(this.holder); 29 | } 30 | 31 | @Mapper() 32 | abstract class PassOnMapperFunctionMapper { 33 | @Mapping(target: "holder", source: mapComplexSubSource) 34 | ComplexFunctionTarget fromSource(ComplexFunctionSource source); 35 | 36 | FunctionSubTarget fromSubSource(FunctionSubSource source); 37 | } 38 | 39 | ComplexFunctionSubTargetHolder mapComplexSubSource(ComplexFunctionSource source, {required PassOnMapperFunctionMapper mapper}) { 40 | final subTarget = mapper.fromSubSource(source.subSource); 41 | final holder = ComplexFunctionSubTargetHolder(); 42 | holder.setData(subTarget); 43 | return holder; 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/list/list_more.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'list_more.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | class ListMapperImpl extends ListMapper { 10 | ListMapperImpl() : super(); 11 | 12 | @override 13 | Target fromSource(Source source) { 14 | final target = Target(); 15 | target.intList = source.intList.map((e) => e).toList(); 16 | target.entryList = source.entryList.map((x) => fromSourceEntry(x)).toList(); 17 | target.entryList2 = 18 | source.entryList2.map((x) => fromSourceEntry(x)).toSet(); 19 | target.entryList3 = source.entryList3.map((x) => fromSourceEntry(x)); 20 | target.entryList4 = source.entryList4.map((x) => fromSourceEntry(x)); 21 | target.entryList5 = (source.entryList5 22 | .map((x) => x == null ? null : fromSourceEntry(x)) 23 | .where((x) => x != null) 24 | .toSet() as Set); 25 | return target; 26 | } 27 | 28 | @override 29 | TargetEntry fromSourceEntry(SourceEntry source) { 30 | final targetentry = TargetEntry(source.prop); 31 | return targetentry; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /generator/test/src/mapper_inheritance_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | class DataContract { 3 | 4 | int id; 5 | 6 | DataContract(this.id); 7 | } 8 | 9 | class DomainEntity { 10 | int id; 11 | 12 | DomainEntity(this.id); 13 | 14 | } 15 | 16 | abstract class ContractFromEntityMapper { 17 | C? fromEntity(E? entity); 18 | } 19 | 20 | class UserLoginContract extends DataContract{ 21 | int age; 22 | 23 | UserLoginContract(this.age, int id) : super(id); 24 | } 25 | 26 | 27 | class UserLoginEntity extends DomainEntity{ 28 | int age; 29 | 30 | UserLoginEntity(this.age, int id) : super(id); 31 | } 32 | 33 | @Mapper() 34 | @ShouldGenerate(r''' 35 | class MapperInheritanceMapperImpl extends MapperInheritanceMapper { 36 | MapperInheritanceMapperImpl() : super(); 37 | 38 | @override 39 | UserLoginContract? fromEntity(UserLoginEntity? entity) { 40 | if (entity == null) { 41 | return null; 42 | } 43 | ; 44 | final userlogincontract = UserLoginContract( 45 | entity.age, 46 | entity.id, 47 | ); 48 | return userlogincontract; 49 | } 50 | } 51 | ''') 52 | abstract class MapperInheritanceMapper extends ContractFromEntityMapper { 53 | } 54 | -------------------------------------------------------------------------------- /generator/test/src/function_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class FunctionFieldSource { 4 | final String fieldTextSource; 5 | 6 | FunctionFieldSource(this.fieldTextSource); 7 | } 8 | 9 | class FunctionFieldTarget { 10 | final String fieldTextTarget; 11 | final num unmappedNum; 12 | final String unmappedText; 13 | 14 | FunctionFieldTarget( 15 | this.fieldTextTarget, this.unmappedNum, this.unmappedText); 16 | } 17 | 18 | @Mapper() 19 | @ShouldGenerate(r''' 20 | class FunctionFieldMapperImpl extends FunctionFieldMapper { 21 | FunctionFieldMapperImpl() : super(); 22 | 23 | @override 24 | FunctionFieldTarget fromSource(FunctionFieldSource source) { 25 | final functionfieldtarget = FunctionFieldTarget( 26 | source.fieldTextSource, 27 | someNum(source), 28 | FunctionFieldMapper.someText(source), 29 | ); 30 | return functionfieldtarget; 31 | } 32 | } 33 | ''') 34 | abstract class FunctionFieldMapper { 35 | static String someText(FunctionFieldSource source) => 'some text'; 36 | @Mapping(source: 'fieldTextSource', target: 'fieldTextTarget') 37 | @Mapping(source: someNum, target: 'unmappedNum') 38 | @Mapping(source: someText, target: 'unmappedText') 39 | FunctionFieldTarget fromSource(FunctionFieldSource source); 40 | } 41 | 42 | num someNum(FunctionFieldSource source) => 2; 43 | -------------------------------------------------------------------------------- /generator/test/mapper_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct_generator/generators/mapper_generator.dart'; 2 | import 'package:source_gen_test/source_gen_test.dart'; 3 | import 'package:path/path.dart' as p; 4 | 5 | Future main() async { 6 | initializeBuildLogTracking(); 7 | final reader = await initializeLibraryReaderForDirectory( 8 | p.join('test', 'src'), 9 | 'mapper_test_input.dart', 10 | ); 11 | 12 | testAnnotatedElements( 13 | reader, 14 | MapperGenerator(), 15 | expectedAnnotatedTests: _expectedAnnotatedTests, 16 | ); 17 | } 18 | 19 | const _expectedAnnotatedTests = { 20 | 'theAnswer', 21 | 'NoReturnTypeMapper', 22 | 'SimpleMapper', 23 | 'ExplicitFieldMapper', 24 | 'NestedMapper', 25 | 'NullableNestedMapper', 26 | 'NestedMappingAfterFunctionMappingMapper', 27 | 'InjectableMapper', 28 | 'ListMapper', 29 | 'NullableListMapper', 30 | "CollectionMapper", 31 | 'CaseInsensitiveMapper', 32 | 'CaseSensitiveMapper', 33 | 'CaseSensitiveDuplicateMapper', 34 | 'ConstructorMapper', 35 | 'FunctionFieldMapper', 36 | 'InheritanceMapper', 37 | 'MultipleSourceMapper', 38 | 'IgnoreMapper', 39 | 'MultiConMapper', 40 | 'NestedMappingMapper', 41 | 'StaticMappingMapper', 42 | 'NestedGenericMapper', 43 | 'PassOnMapperFunctionMapper', 44 | 'StaticProxyMapper', 45 | 'MapperInheritanceMapper', 46 | }; 47 | -------------------------------------------------------------------------------- /generator/test/src/inheritance_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | abstract class SuperSource { 4 | final String superText; 5 | String get abstractGetterText; 6 | 7 | String get mappable { 8 | return 'mappable'; 9 | } 10 | 11 | SuperSource(this.superText); 12 | } 13 | 14 | class InheritanceSource extends SuperSource { 15 | final num number; 16 | 17 | @override 18 | final String abstractGetterText; 19 | 20 | InheritanceSource(this.abstractGetterText, this.number, String superText) 21 | : super(superText); 22 | } 23 | 24 | class InheritanceTarget { 25 | final String abstractGetterText; 26 | final num number; 27 | final String superText; 28 | final String mappable; 29 | 30 | InheritanceTarget( 31 | this.abstractGetterText, this.number, this.superText, this.mappable); 32 | } 33 | 34 | @Mapper() 35 | @ShouldGenerate(r''' 36 | class InheritanceMapperImpl extends InheritanceMapper { 37 | InheritanceMapperImpl() : super(); 38 | 39 | @override 40 | InheritanceTarget fromSource(InheritanceSource source) { 41 | final inheritancetarget = InheritanceTarget( 42 | source.abstractGetterText, 43 | source.number, 44 | source.superText, 45 | source.mappable, 46 | ); 47 | return inheritancetarget; 48 | } 49 | } 50 | ''') 51 | abstract class InheritanceMapper { 52 | InheritanceTarget fromSource(InheritanceSource source); 53 | } 54 | -------------------------------------------------------------------------------- /generator/test/src/nested_mapper_in_mapping_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class User { 4 | final String username; 5 | final String zipcode; 6 | final String street; 7 | 8 | User(this.username, this.zipcode, this.street); 9 | } 10 | 11 | class UserResponse { 12 | final String username; 13 | final AddressResponse address; 14 | 15 | UserResponse(this.username, this.address); 16 | } 17 | 18 | class AddressResponse { 19 | final String zipcode; 20 | final StreetResponse street; 21 | 22 | AddressResponse(this.zipcode, this.street); 23 | } 24 | 25 | class StreetResponse { 26 | final num streetNumber; 27 | final String streetName; 28 | 29 | StreetResponse(this.streetNumber, this.streetName); 30 | } 31 | 32 | @Mapper() 33 | @ShouldGenerate(r''' 34 | class NestedMappingMapperImpl extends NestedMappingMapper { 35 | NestedMappingMapperImpl() : super(); 36 | 37 | @override 38 | User fromResponse(UserResponse response) { 39 | final user = User( 40 | response.username, 41 | response.address.zipcode, 42 | response.address.street.streetName, 43 | ); 44 | return user; 45 | } 46 | } 47 | ''') 48 | @Mapper() 49 | abstract class NestedMappingMapper { 50 | @Mapping(target: 'zipcode', source: 'response.address.zipcode') 51 | @Mapping(target: 'street', source: 'response.address.street.streetName') 52 | User fromResponse(UserResponse response); 53 | } 54 | -------------------------------------------------------------------------------- /generator/test/src/nested_generic_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class NestedGeneric { 4 | 5 | T value; 6 | 7 | NestedGeneric(this.value); 8 | } 9 | 10 | class NestedGenericSource { 11 | NestedGeneric data; 12 | 13 | NestedGenericSource(this.data); 14 | } 15 | 16 | 17 | class NestedGenericSubSource {} 18 | 19 | class NestedGenericTarget { 20 | 21 | NestedGenericSubTarget data; 22 | 23 | NestedGenericTarget(this.data); 24 | } 25 | 26 | 27 | class NestedGenericSubTarget { 28 | } 29 | 30 | 31 | @Mapper() 32 | @ShouldGenerate(''' 33 | class NestedGenericMapperImpl extends NestedGenericMapper { 34 | NestedGenericMapperImpl() : super(); 35 | 36 | @override 37 | NestedGenericTarget fromSource(NestedGenericSource source) { 38 | final nestedgenerictarget = 39 | NestedGenericTarget(fromSubSource(source.data.value)); 40 | return nestedgenerictarget; 41 | } 42 | 43 | @override 44 | NestedGenericSubTarget fromSubSource(NestedGenericSubSource source) { 45 | final nestedgenericsubtarget = NestedGenericSubTarget(); 46 | return nestedgenericsubtarget; 47 | } 48 | } 49 | ''') 50 | abstract class NestedGenericMapper { 51 | 52 | @Mapping(target: "data", source: "source.data.value") 53 | NestedGenericTarget fromSource(NestedGenericSource source); 54 | 55 | NestedGenericSubTarget fromSubSource(NestedGenericSubSource source); 56 | } -------------------------------------------------------------------------------- /generator/lib/generators/mapper_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:analyzer/dart/element/type.dart'; 4 | import 'package:build/src/builder/build_step.dart'; 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:code_builder/code_builder.dart'; 7 | import 'package:smartstruct/smartstruct.dart'; 8 | import 'package:smartstruct_generator/code_builders/class_builder.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | import '../mapper_config.dart'; 11 | 12 | /// Codegenerator to generate implemented mapping classes 13 | class MapperGenerator extends GeneratorForAnnotation { 14 | @override 15 | dynamic generateForAnnotatedElement( 16 | Element element, ConstantReader annotation, BuildStep buildStep) { 17 | if (element is! ClassElement) { 18 | throw InvalidGenerationSourceError( 19 | '${element.displayName} is not a class and cannot be annotated with @Mapper', 20 | element: element, 21 | todo: 'Add Mapper annotation to a class'); 22 | } 23 | 24 | var config = MapperConfig.readMapperConfig(annotation, element); 25 | 26 | try { 27 | final mapperImpl = buildMapperClass(element, config); 28 | final emitter = DartEmitter(); 29 | return '${mapperImpl.accept(emitter)}'; 30 | } on Error catch (e) { 31 | print(e); 32 | print(e.stackTrace); 33 | rethrow; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/complete/complete.mapper.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'complete.dart'; 4 | 5 | // ************************************************************************** 6 | // MapperGenerator 7 | // ************************************************************************** 8 | 9 | @LazySingleton(as: ExampleMapper) 10 | class ExampleMapperImpl extends ExampleMapper { 11 | ExampleMapperImpl() : super(); 12 | 13 | @override 14 | BarTarget fromFoo(FooSource source, FooSourceTheSecond second) { 15 | final bartarget = BarTarget( 16 | source.number, source.text, source.truthy, source.superText, 17 | named: source.named, 18 | namedTwoDiff: source.namedTwo, 19 | list: source.list.map((x) => fromFooSub(x)).toList(), 20 | secondTextOther: second.secondTextOther); 21 | bartarget.property = source.property; 22 | bartarget.propertyTwoDiff = source.propertyTwo; 23 | bartarget.nested = fromFooSub(source.nested); 24 | bartarget.superPropertySet = source.superPropertySet; 25 | bartarget.secondText = second.secondText; 26 | bartarget.setterTextDiff = source.setterText; 27 | bartarget.setterNumber = source.setterNumber; 28 | return bartarget; 29 | } 30 | 31 | @override 32 | BarNestedTarget fromFooSub(FooNestedSource source) { 33 | final barnestedtarget = BarNestedTarget(source.text, source.number); 34 | return barnestedtarget; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /generator/lib/code_builders/static_proxy_builder.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:analyzer/dart/element/element.dart'; 3 | import 'package:code_builder/code_builder.dart'; 4 | import 'package:smartstruct_generator/code_builders/parameter_copy.dart'; 5 | 6 | Class generateStaticProxy(ClassElement abstractMapper) { 7 | return Class( 8 | (b) => b 9 | ..name = '${abstractMapper.displayName}\$' 10 | ..fields.add(Field((f) => 11 | f..name = "mapper" 12 | ..modifier = FieldModifier.final$ 13 | ..type = refer(abstractMapper.thisType.getDisplayString(withNullability: true)) 14 | ..assignment = refer('${abstractMapper.name}Impl()').code 15 | ..static = true 16 | )) 17 | ..methods.addAll(abstractMapper.methods 18 | .where((method) => method.isAbstract) 19 | .map((method) => 20 | buildStaticMethod(method, abstractMapper))), 21 | ); 22 | } 23 | 24 | Method buildStaticMethod(MethodElement method, ClassElement element) { 25 | final argList = method.parameters.map((e) => copyParameter(e)); 26 | final argNameList = method.parameters.map((e) => refer(e.name)); 27 | return Method((b) => 28 | b..static = true 29 | ..name = method.displayName 30 | ..requiredParameters.addAll(argList) 31 | ..body = refer("mapper.${method.displayName}").call(argNameList).code 32 | ..returns = refer(method.returnType.getDisplayString(withNullability: true)) 33 | ); 34 | } -------------------------------------------------------------------------------- /generator/test/src/mapper_test_input.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:smartstruct/smartstruct.dart'; 4 | import 'package:source_gen_test/source_gen_test.dart'; 5 | 6 | part 'case_sensitive_mapper_input.dart'; 7 | part 'collection_mapper_input.dart'; 8 | part 'constructor_mapper_input.dart'; 9 | part 'explicit_field_mapper_input.dart'; 10 | part 'function_mapper_input.dart'; 11 | part 'ignore_field_mapper_input.dart'; 12 | part 'inheritance_mapper_input.dart'; 13 | part 'injectable_mapper_input.dart'; 14 | part 'list_mapper_input.dart'; 15 | part 'mapper_inheritance_input.dart'; 16 | part 'multiple_constructor_input.dart'; 17 | part 'multiple_source_mapper_input.dart'; 18 | part 'nested_generic_mapper_input.dart'; 19 | part 'nested_mapper_in_mapping_input.dart'; 20 | part 'nested_mapper_input.dart'; 21 | part 'nested_mapping_after_function_mapping_input.dart'; 22 | part 'nullable_list_mapper_input.dart'; 23 | part 'nullable_nested_mapper_input.dart'; 24 | part 'pass_on_mapper_function_mapper_input.dart'; 25 | part 'simple_mapper_input.dart'; 26 | part 'static_mapper_input.dart'; 27 | part 'static_proxy_input.dart'; 28 | 29 | @ShouldThrow('theAnswer is not a class and cannot be annotated with @Mapper') 30 | @Mapper() 31 | const theAnswer = 42; 32 | 33 | class NoReturnTypeSource {} 34 | 35 | @Mapper() 36 | @ShouldThrow('void is not a valid return type') 37 | abstract class NoReturnTypeMapper { 38 | void fromSource(NoReturnTypeSource source); 39 | } 40 | -------------------------------------------------------------------------------- /generator/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Nils 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /generator/test/src/nullable_nested_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class NullableNestedSource { 4 | 5 | final NestedSubSource? nested; 6 | 7 | NullableNestedSource(this.nested); 8 | } 9 | 10 | 11 | class NullableNestedTarget { 12 | 13 | final NestedSubTarget? nested; 14 | 15 | NullableNestedTarget(this.nested); 16 | } 17 | 18 | @Mapper() 19 | @ShouldGenerate(''' 20 | class NullableNestedMapperImpl extends NullableNestedMapper { 21 | NullableNestedMapperImpl() : super(); 22 | 23 | @override 24 | NullableNestedTarget fromSource(NestedSource source) { 25 | final nullablenestedtarget = 26 | NullableNestedTarget(fromSubSource(source.nested)); 27 | return nullablenestedtarget; 28 | } 29 | 30 | @override 31 | NullableNestedTarget fromNullableSource(NullableNestedSource source) { 32 | final nullablenestedtarget = NullableNestedTarget( 33 | source.nested == null ? null : fromSubSource(source.nested!)); 34 | return nullablenestedtarget; 35 | } 36 | 37 | @override 38 | NestedSubTarget fromSubSource(NestedSubSource source) { 39 | final nestedsubtarget = NestedSubTarget(source.text); 40 | return nestedsubtarget; 41 | } 42 | } 43 | ''') 44 | abstract class NullableNestedMapper { 45 | NullableNestedTarget fromSource(NestedSource source); 46 | NullableNestedTarget fromNullableSource(NullableNestedSource source); 47 | 48 | NestedSubTarget fromSubSource(NestedSubSource source); 49 | } 50 | -------------------------------------------------------------------------------- /smartstruct/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Nils 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /smartstruct/lib/src/annotations.dart: -------------------------------------------------------------------------------- 1 | /// Main Mapper Annotation. 2 | /// 3 | /// Annotate your interface with [mapper], and run your build_runner to generate an implemented mapperclass of this interface. 4 | class Mapper { 5 | final bool useInjection; 6 | final bool caseSensitiveFields; 7 | final bool generateStaticProxy; 8 | 9 | const Mapper({this.useInjection = false, this.caseSensitiveFields = false, this.generateStaticProxy = false}); 10 | } 11 | 12 | const mapper = Mapper(); 13 | 14 | /// Mapping Annotation to support explicit field mapping in case the mapped source and target attribute do not match in name 15 | /// 16 | /// Annotate the method with [Mapping] and provide a valid source and target fieldname to map these two fields with each other 17 | class Mapping { 18 | final dynamic source; 19 | final String target; 20 | final bool ignore; 21 | const Mapping({required this.target, this.source, this.ignore = false}); 22 | } 23 | 24 | /// Annotate methods to indicate that you don't want smartstruct to generate a mapping implementation for you 25 | /// 26 | /// Example 27 | /// ```dart 28 | /// @Mapper() 29 | /// abstract class UserMapper { 30 | /// @IgnoreMapping // no method will be generated 31 | /// static EnumTargetFoo mapFoo(UserSource source) => EnumTargetFoo.ONE; // no static method will be generated 32 | /// static UserTarget fromSourceStatic(UserSource source) => _$fromSourceStatic(source); 33 | /// ... 34 | /// } 35 | /// ``` 36 | class IgnoreMapping { 37 | const IgnoreMapping(); 38 | } -------------------------------------------------------------------------------- /example/lib/nested/nested_nullable_mapping.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:smartstruct/smartstruct.dart'; 4 | part 'nested_nullable_mapping.mapper.g.dart'; 5 | 6 | class NullableNestedSource { 7 | 8 | final NestedSubSource? nested; 9 | 10 | NullableNestedSource(this.nested); 11 | } 12 | 13 | class NestedSource { 14 | NestedSubSource nested; 15 | 16 | NestedSource(this.nested); 17 | } 18 | 19 | class NestedSubSource { 20 | } 21 | 22 | 23 | class NullableNestedTarget { 24 | 25 | final NestedSubTarget? nested; 26 | 27 | NullableNestedTarget(this.nested); 28 | } 29 | 30 | class NestedSubTarget { 31 | } 32 | 33 | @Mapper() 34 | abstract class NullableNestedMapper { 35 | // The type of "NullableNestedTarget.nested" is nullable and the return 36 | // of the nested mapping "fromSubSource" is NestedSubTarget which is 37 | // not nullable. In this case the nested mapping is matched. 38 | NullableNestedTarget fromSource(NestedSource source); 39 | 40 | // The type of "NullableNestedSource.nested" is nullable, but the input 41 | // type of the nested mapping "fromSource" is not nullable. The 42 | // type of "NullableNestedTarget.nested" is also nullable. In this case 43 | // the nested mapping is also matched, but a little more work to do. We need to 44 | // check if the "source.nested" is null and determine whether apply the nested 45 | // mapping or only return the "null" directly. 46 | NullableNestedTarget fromNullableSource(NullableNestedSource source); 47 | 48 | NestedSubTarget fromSubSource(NestedSubSource source); 49 | } 50 | -------------------------------------------------------------------------------- /example/lib/function_mapping/function_mapping_more.dart: -------------------------------------------------------------------------------- 1 | import 'package:smartstruct/smartstruct.dart'; 2 | import 'package:smartstruct_example/function_mapping/function_utils.dart'; 3 | part 'function_mapping_more.mapper.g.dart'; 4 | 5 | // TARGET 6 | 7 | class Dog { 8 | final String name; 9 | final String breed; 10 | final int age; 11 | late AgeHolderTarget? model; 12 | 13 | Dog(this.name, this.breed, this.age); 14 | } 15 | 16 | // SOURCE 17 | 18 | class DogModel { 19 | final String name; 20 | final int age; 21 | 22 | DogModel(this.name, this.age); 23 | } 24 | 25 | class AgeHolderSource { 26 | final int age; 27 | AgeHolderSource(this.age); 28 | } 29 | 30 | class AgeHolderTarget { 31 | late int age; 32 | } 33 | 34 | // This is a more complex example. 35 | // The toAgeHolderSource may return a "null" and the nested mapping "fromDogModel" 36 | // is not accept "null". 37 | // So we need to invoke "toAgeHolderSource" and assign the 38 | // return the a variable "tmp". And determine whether use the "nested mapping" by 39 | // case that the value of "tmp" is null or not. 40 | @Mapper() 41 | abstract class DogMapper { 42 | static String breedCustom(DogModel model) => 'customBreed'; 43 | 44 | @Mapping(source: fullNameWithAge, target: 'name') 45 | @Mapping(source: breedCustom, target: 'breed') 46 | @Mapping(source: toAgeHolderSource, target: 'model') 47 | Dog fromDogModel(DogModel model); 48 | 49 | AgeHolderTarget fromAgeHolderSource(AgeHolderSource model); 50 | } 51 | 52 | String fullNameWithAge(DogModel model) => model.name + '${model.age}'; 53 | AgeHolderSource? toAgeHolderSource(DogModel model) => AgeHolderSource(model.age); -------------------------------------------------------------------------------- /example/lib/list/list_more.dart: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import 'dart:collection'; 3 | 4 | import 'package:smartstruct/smartstruct.dart'; 5 | 6 | part 'list_more.mapper.g.dart'; 7 | 8 | class Source { 9 | final List intList; 10 | final List entryList; 11 | final Iterable entryList2; 12 | final Set entryList3; 13 | final CustomList entryList4; 14 | final List entryList5; 15 | 16 | Source(this.intList, this.entryList, this.entryList2, this.entryList3, this.entryList4, this.entryList5); 17 | } 18 | 19 | class SourceEntry { 20 | final String prop; 21 | 22 | SourceEntry(this.prop); 23 | } 24 | 25 | // TARGET 26 | class Target { 27 | late List intList; 28 | late List entryList; 29 | late Set entryList2; 30 | late Iterable entryList3; 31 | late Iterable entryList4; 32 | late Set entryList5; 33 | 34 | Target(); 35 | } 36 | 37 | class TargetEntry { 38 | final String prop; 39 | 40 | TargetEntry(this.prop); 41 | } 42 | 43 | @Mapper() 44 | abstract class ListMapper { 45 | Target fromSource(Source source); 46 | TargetEntry fromSourceEntry(SourceEntry source); 47 | } 48 | 49 | class CustomList extends ListMixin{ 50 | 51 | List list; 52 | 53 | CustomList(this.list); 54 | 55 | @override 56 | int get length => list.length; 57 | 58 | @override 59 | operator [](int index) { 60 | return list.elementAt(index); 61 | } 62 | 63 | @override 64 | void operator []=(int index, value) { 65 | list[index] = value; 66 | } 67 | 68 | @override 69 | set length(int newLength) { 70 | } 71 | } -------------------------------------------------------------------------------- /generator/test/src/case_sensitive_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class Source { 4 | final String username; 5 | 6 | Source(this.username); 7 | } 8 | 9 | class Target { 10 | final String userName; 11 | Target(this.userName); 12 | } 13 | 14 | class Duplicated { 15 | final String username; 16 | final String userNAME; 17 | 18 | Duplicated(this.username, this.userNAME); 19 | } 20 | 21 | @Mapper() 22 | @ShouldGenerate(r''' 23 | class CaseInsensitiveMapperImpl extends CaseInsensitiveMapper { 24 | CaseInsensitiveMapperImpl() : super(); 25 | 26 | @override 27 | Target fromSource(Source source) { 28 | final target = Target(source.username); 29 | return target; 30 | } 31 | } 32 | ''') 33 | abstract class CaseInsensitiveMapper { 34 | Target fromSource(Source source); 35 | } 36 | 37 | @Mapper(caseSensitiveFields: true) 38 | @ShouldGenerate(r''' 39 | class CaseSensitiveMapperImpl extends CaseSensitiveMapper { 40 | CaseSensitiveMapperImpl() : super(); 41 | 42 | @override 43 | Target fromSource(Source source) { 44 | final target = Target(); 45 | return target; 46 | } 47 | } 48 | ''') 49 | abstract class CaseSensitiveMapper { 50 | Target fromSource(Source source); 51 | } 52 | 53 | @Mapper(caseSensitiveFields: true) 54 | @ShouldGenerate(r''' 55 | class CaseSensitiveDuplicateMapperImpl extends CaseSensitiveDuplicateMapper { 56 | CaseSensitiveDuplicateMapperImpl() : super(); 57 | 58 | @override 59 | Duplicated fromSource(Duplicated source) { 60 | final duplicated = Duplicated( 61 | source.username, 62 | source.userNAME, 63 | ); 64 | return duplicated; 65 | } 66 | } 67 | ''') 68 | abstract class CaseSensitiveDuplicateMapper { 69 | Duplicated fromSource(Duplicated source); 70 | } 71 | -------------------------------------------------------------------------------- /generator/test/src/pass_on_mapper_function_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class FunctionSubSource { 4 | } 5 | 6 | class FunctionSubTarget { 7 | } 8 | 9 | class ComplexFunctionSubTargetHolder { 10 | 11 | FunctionSubTarget? _data; 12 | 13 | setData(FunctionSubTarget data) { 14 | _data = data; 15 | } 16 | } 17 | 18 | class ComplexFunctionSource { 19 | FunctionSubSource subSource; 20 | 21 | ComplexFunctionSource(this.subSource); 22 | } 23 | 24 | class ComplexFunctionTarget { 25 | ComplexFunctionSubTargetHolder holder; 26 | 27 | ComplexFunctionTarget(this.holder); 28 | } 29 | 30 | @Mapper() 31 | @ShouldGenerate(''' 32 | class PassOnMapperFunctionMapperImpl extends PassOnMapperFunctionMapper { 33 | PassOnMapperFunctionMapperImpl() : super(); 34 | 35 | @override 36 | ComplexFunctionTarget fromSource(ComplexFunctionSource source) { 37 | final complexfunctiontarget = ComplexFunctionTarget(mapComplexSubSource( 38 | source, 39 | mapper: this, 40 | )); 41 | return complexfunctiontarget; 42 | } 43 | 44 | @override 45 | FunctionSubTarget fromSubSource(FunctionSubSource source) { 46 | final functionsubtarget = FunctionSubTarget(); 47 | return functionsubtarget; 48 | } 49 | } 50 | ''') 51 | abstract class PassOnMapperFunctionMapper { 52 | @Mapping(target: "holder", source: mapComplexSubSource) 53 | ComplexFunctionTarget fromSource(ComplexFunctionSource source); 54 | 55 | FunctionSubTarget fromSubSource(FunctionSubSource source); 56 | } 57 | 58 | ComplexFunctionSubTargetHolder mapComplexSubSource(ComplexFunctionSource source, {required PassOnMapperFunctionMapper mapper}) { 59 | final subTarget = mapper.fromSubSource(source.subSource); 60 | final holder = ComplexFunctionSubTargetHolder(); 61 | holder.setData(subTarget); 62 | return holder; 63 | } 64 | -------------------------------------------------------------------------------- /generator/lib/models/source_assignment.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/dart/element/type.dart'; 3 | import 'package:smartstruct_generator/models/RefChain.dart'; 4 | 5 | class SourceAssignment { 6 | FieldElement? field; 7 | String? sourceName; 8 | 9 | ExecutableElement? function; 10 | List? params; 11 | 12 | RefChain? refChain; 13 | 14 | SourceAssignment.fromField(this.field, this.sourceName); 15 | SourceAssignment.fromFunction(this.function, this.params); 16 | SourceAssignment.fromRefChain(this.refChain) { 17 | sourceName = refChain!.removeLast().refWithQuestion; 18 | field = refChain?.elementList.last as FieldElement; 19 | } 20 | 21 | bool shouldAssignList(DartType targetFieldType) { 22 | // The source can be mapped to the target, if the source is mapable object and the target is listLike. 23 | return _isCoreListLike(targetFieldType) && _isMapable(field!.type); 24 | } 25 | 26 | bool _isCoreListLike(DartType type) { 27 | return type.isDartCoreList || type.isDartCoreSet || type.isDartCoreIterable; 28 | } 29 | 30 | bool _isMapable(DartType type) { 31 | if(_isCoreListLike(type)) { 32 | return true; 33 | } 34 | 35 | if(type is! InterfaceType) { 36 | return false; 37 | } 38 | return type.allSupertypes.any(_isCoreListLike); 39 | } 40 | 41 | String collectInvoke(DartType type) { 42 | if(type.isDartCoreList) { 43 | return "toList()"; 44 | } else if(type.isDartCoreSet) { 45 | return "toSet()"; 46 | } 47 | throw "Unkown type ${type.getDisplayString(withNullability: false)}"; 48 | } 49 | 50 | bool needCollect(DartType targetType) { 51 | return !targetType.isDartCoreIterable; 52 | } 53 | 54 | bool shouldUseFunction() { 55 | return function != null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /generator/test/src/nested_mapping_after_function_mapping_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class NestedSubSourceRef { 4 | final NestedSubSource _subSource; 5 | NestedSubSourceRef sourceNullableRef; 6 | 7 | NestedSubSourceRef(this._subSource, this.sourceNullableRef); 8 | 9 | getSubSource() { 10 | return _subSource; 11 | } 12 | } 13 | 14 | class NestedRefSource { 15 | NestedSubSourceRef ref; 16 | 17 | NestedRefSource(this.ref); 18 | } 19 | 20 | class NullableNestedRefSource { 21 | NestedSubSourceRef? ref; 22 | 23 | NullableNestedRefSource(this.ref); 24 | } 25 | 26 | 27 | @Mapper() 28 | @ShouldGenerate(''' 29 | class NestedMappingAfterFunctionMappingMapperImpl 30 | extends NestedMappingAfterFunctionMappingMapper { 31 | NestedMappingAfterFunctionMappingMapperImpl() : super(); 32 | 33 | @override 34 | NestedTarget fromRefSource(NestedRefSource source) { 35 | final nestedtarget = NestedTarget(fromSubSource(mapRef(source))); 36 | return nestedtarget; 37 | } 38 | 39 | @override 40 | NullableNestedTarget fromRefSource2(NullableNestedRefSource source) { 41 | final nullablenestedtarget = NullableNestedTarget(() { 42 | final tmp = nullableMapRef(source); 43 | return tmp == null ? null : fromSubSource(tmp); 44 | }()); 45 | return nullablenestedtarget; 46 | } 47 | 48 | @override 49 | NestedSubTarget fromSubSource(NestedSubSource source) { 50 | final nestedsubtarget = NestedSubTarget(source.text); 51 | return nestedsubtarget; 52 | } 53 | } 54 | ''') 55 | abstract class NestedMappingAfterFunctionMappingMapper { 56 | 57 | @Mapping(target: "nested", source: mapRef) 58 | NestedTarget fromRefSource(NestedRefSource source); 59 | 60 | @Mapping(target: "nested", source: nullableMapRef) 61 | NullableNestedTarget fromRefSource2(NullableNestedRefSource source); 62 | 63 | NestedSubTarget fromSubSource(NestedSubSource source); 64 | 65 | } 66 | 67 | NestedSubSource mapRef(NestedRefSource source) => source.ref.getSubSource(); 68 | NestedSubSource? nullableMapRef(NullableNestedRefSource source) => source.ref?.getSubSource(); -------------------------------------------------------------------------------- /generator/lib/mapper_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/constant/value.dart'; 2 | import 'package:analyzer/dart/element/element.dart'; 3 | import 'package:smartstruct/smartstruct.dart'; 4 | import 'package:source_gen/source_gen.dart'; 5 | 6 | /// Helper Class to read attributes out of the [Mapper] and [Mapping] Annotations 7 | class MapperConfig { 8 | /// Reads the attributes given in the [Mapper] Annotation, 9 | /// and returns a map, where the key is the attributename, and value the value of the read attribute. 10 | static Map readMapperConfig( 11 | ConstantReader annotation, ClassElement mappingClass) { 12 | var mapper = 13 | mappingClass.metadata[0].element!.enclosingElement3 as ClassElement; 14 | final config = {}; 15 | 16 | for (final field in mapper.fields) { 17 | final configField = annotation.read(field.name).literalValue; 18 | config.putIfAbsent(field.name, () => configField); 19 | } 20 | return config; 21 | } 22 | 23 | /// Reads the attributes of the [Mapping] annotations of a given Method, 24 | /// and returns key value pairs, where the key is the target, and the value is an instance of [MappingConfig] containing the source and other meta attributes 25 | static Map readMappingConfig(MethodElement method) { 26 | final config = {}; 27 | final annotations = TypeChecker.fromRuntime(Mapping).annotationsOf(method); 28 | 29 | for (final element in annotations) { 30 | final reader = ConstantReader(element); 31 | final sourceReader = reader.read('source'); 32 | config[reader.read('target').stringValue] = MappingConfig( 33 | sourceReader.isNull ? null : sourceReader.objectValue, 34 | reader.read('ignore').boolValue); 35 | } 36 | return config; 37 | } 38 | 39 | static bool isIgnoreMapping(MethodElement method) { 40 | final annotations = TypeChecker.fromRuntime(IgnoreMapping).annotationsOf(method); 41 | return annotations.isNotEmpty; 42 | } 43 | } 44 | 45 | class MappingConfig { 46 | final DartObject? source; 47 | final bool ignore; 48 | 49 | MappingConfig(this.source, this.ignore); 50 | } 51 | -------------------------------------------------------------------------------- /generator/test/src/static_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | enum StaticEnumTarget { 4 | ONE,TWO,THREE 5 | } 6 | 7 | class StaticMappingTarget { 8 | final String text; 9 | final num number; 10 | final StaticEnumTarget foo; 11 | 12 | StaticMappingTarget(this.text, this.number, this.foo); 13 | } 14 | 15 | class StaticMappingSource { 16 | final String text; 17 | final num number; 18 | 19 | StaticMappingSource(this.text, this.number); 20 | } 21 | 22 | @Mapper() 23 | @ShouldGenerate(r''' 24 | class StaticMappingMapperImpl extends StaticMappingMapper { 25 | StaticMappingMapperImpl() : super(); 26 | 27 | @override 28 | StaticMappingTarget fromSourceNormal(StaticMappingSource source) { 29 | final staticmappingtarget = StaticMappingTarget( 30 | source.text, 31 | source.number, 32 | ); 33 | return staticmappingtarget; 34 | } 35 | } 36 | 37 | StaticMappingTarget _$fromSourceStatic(StaticMappingSource source) { 38 | final staticmappingtarget = StaticMappingTarget( 39 | source.text, 40 | source.number, 41 | StaticMappingMapper.mapEnum(source), 42 | ); 43 | return staticmappingtarget; 44 | } 45 | ''') 46 | abstract class StaticMappingMapper { 47 | // mapper for primitive types should never be generated by default 48 | static String ignoredPrimitiveTypeMethod() => "ignore me"; 49 | // these helpers should not be generated as they are ignored by default 50 | static StaticEnumTarget mapEnum(StaticMappingSource source) => StaticEnumTarget.ONE; 51 | static List mapEnumList(StaticMappingSource source) => [StaticEnumTarget.ONE, StaticEnumTarget.THREE]; 52 | static Set mapSomeSet(StaticMappingSource source) => {"1", "2"}; 53 | 54 | // helper should be ignored as it is explicitly annotated 55 | @IgnoreMapping() 56 | static StaticMappingTarget? fromSourceStaticIgnored(StaticMappingSource source) => null; 57 | 58 | // this helper should be generated as it is explicitly configured to do so via the StaticMapping Annotation 59 | @Mapping(source: mapEnum, target: 'foo') 60 | static StaticMappingTarget fromSourceStatic(StaticMappingSource source) => _$fromSourceStatic(source); 61 | 62 | StaticMappingTarget fromSourceNormal(StaticMappingSource source); 63 | } 64 | 65 | // just so the test compiles 66 | StaticMappingTarget _$fromSourceStatic(StaticMappingSource source) => StaticMappingTarget("text", 1, StaticEnumTarget.THREE); 67 | -------------------------------------------------------------------------------- /generator/test/src/collection_mapper_input.dart: -------------------------------------------------------------------------------- 1 | part of 'mapper_test_input.dart'; 2 | 3 | class CollectionSubSource { 4 | final String text; 5 | 6 | CollectionSubSource(this.text); 7 | } 8 | 9 | 10 | class CollectionSubTarget { 11 | final String text; 12 | 13 | CollectionSubTarget(this.text); 14 | } 15 | 16 | class CustomList extends ListMixin{ 17 | 18 | List list; 19 | 20 | CustomList(this.list); 21 | 22 | @override 23 | int get length => list.length; 24 | 25 | @override 26 | operator [](int index) { 27 | return list.elementAt(index); 28 | } 29 | 30 | @override 31 | void operator []=(int index, value) { 32 | list[index] = value; 33 | } 34 | 35 | @override 36 | set length(int newLength) { 37 | } 38 | } 39 | 40 | class CollectionSource { 41 | 42 | final CustomList customToList; 43 | final Set set; 44 | final Set setToList; 45 | final List listToSet; 46 | final Iterable iterable; 47 | 48 | CollectionSource( 49 | this.customToList, 50 | this.set, 51 | this.setToList, 52 | this.listToSet, 53 | this.iterable, 54 | ); 55 | } 56 | 57 | class CollectionTarget { 58 | 59 | final List customToList; 60 | final Set set; 61 | final List setToList; 62 | final Set listToSet; 63 | final Iterable iterable; 64 | 65 | CollectionTarget( 66 | this.customToList, 67 | this.set, 68 | this.setToList, 69 | this.listToSet, 70 | this.iterable, 71 | ); 72 | } 73 | 74 | 75 | @Mapper() 76 | @ShouldGenerate(r''' 77 | class CollectionMapperImpl extends CollectionMapper { 78 | CollectionMapperImpl() : super(); 79 | 80 | @override 81 | CollectionTarget fromSource(CollectionSource source) { 82 | final collectiontarget = CollectionTarget( 83 | source.customToList.map((x) => fromSubSource(x)).toList(), 84 | source.set.map((x) => fromSubSource(x)).toSet(), 85 | source.setToList.map((x) => fromSubSource(x)).toList(), 86 | source.listToSet.map((x) => fromSubSource(x)).toSet(), 87 | source.iterable.map((x) => fromSubSource(x)), 88 | ); 89 | return collectiontarget; 90 | } 91 | 92 | @override 93 | CollectionSubTarget fromSubSource(CollectionSubSource source) { 94 | final collectionsubtarget = CollectionSubTarget(source.text); 95 | return collectionsubtarget; 96 | } 97 | } 98 | ''') 99 | abstract class CollectionMapper { 100 | CollectionTarget fromSource(CollectionSource source); 101 | CollectionSubTarget fromSubSource(CollectionSubSource source); 102 | } 103 | -------------------------------------------------------------------------------- /example/lib/complete/complete.dart: -------------------------------------------------------------------------------- 1 | import 'package:injectable/injectable.dart'; 2 | import 'package:smartstruct/smartstruct.dart'; 3 | 4 | part 'complete.mapper.g.dart'; 5 | 6 | class FooSourceTheSecond { 7 | String? secondText; 8 | final String secondTextOther; 9 | 10 | FooSourceTheSecond(this.secondTextOther); 11 | } 12 | 13 | abstract class SuperFooSource { 14 | final String superText; 15 | 16 | String get superGet; 17 | 18 | String? _superPropertySet; 19 | 20 | String? get superPropertySet => _superPropertySet; 21 | 22 | set superPropertySet(String? superPropertySet) { 23 | _superPropertySet = superPropertySet; 24 | } 25 | 26 | SuperFooSource(this.superText); 27 | } 28 | 29 | class FooSource extends SuperFooSource { 30 | final num number; 31 | final String text; 32 | final bool truthy; 33 | final String named; 34 | final String namedTwo; 35 | String property; 36 | String propertyTwo; 37 | num? setterNumber; 38 | String? setterText; 39 | late FooNestedSource nested; 40 | final List list; 41 | @override 42 | final String superGet; 43 | FooSource( 44 | this.number, 45 | this.text, 46 | this.truthy, 47 | this.named, 48 | this.setterNumber, 49 | this.property, 50 | this.propertyTwo, 51 | this.namedTwo, 52 | this.list, 53 | this.superGet, 54 | String superText) 55 | : super(superText); 56 | } 57 | 58 | class FooNestedSource { 59 | late String text; 60 | final num number; 61 | 62 | FooNestedSource(this.number); 63 | } 64 | 65 | class BarTarget { 66 | final num numberDiff; 67 | final String text; 68 | final bool truthy; 69 | String named; 70 | String namedTwoDiff; 71 | String? property; 72 | String? propertyTwoDiff; 73 | num? _setterNumber; 74 | String? _setterTextDiff; 75 | late BarNestedTarget nested; 76 | final List list; 77 | final String superText; 78 | String? superPropertySet; 79 | String? secondText; 80 | final String secondTextOther; 81 | 82 | String? get setterTextDiff => _setterTextDiff; 83 | 84 | set setterTextDiff(String? setterTextDiff) { 85 | _setterTextDiff = setterTextDiff; 86 | } 87 | 88 | num? get setterNumber => _setterNumber; 89 | 90 | set setterNumber(num? setterNumber) { 91 | _setterNumber = setterNumber; 92 | } 93 | 94 | BarTarget(this.numberDiff, this.text, this.truthy, this.superText, 95 | {required this.named, 96 | required this.namedTwoDiff, 97 | required this.list, 98 | required this.secondTextOther}); 99 | } 100 | 101 | class BarNestedTarget { 102 | final String text; 103 | final num number; 104 | 105 | BarNestedTarget(this.text, this.number); 106 | } 107 | 108 | /// Mapper showcasing every feature, 109 | /// such as explicit fieldmapping, injection and mapping nested classes 110 | @Mapper(useInjection: true) 111 | abstract class ExampleMapper { 112 | static ExampleMapper get instance => ExampleMapperImpl(); 113 | @Mapping(source: 'number', target: 'numberDiff') 114 | @Mapping(source: 'namedTwo', target: 'namedTwoDiff') 115 | @Mapping(source: 'setterText', target: 'setterTextDiff') 116 | @Mapping(source: 'propertyTwo', target: 'propertyTwoDiff') 117 | BarTarget fromFoo(FooSource source, FooSourceTheSecond second); 118 | 119 | BarNestedTarget fromFooSub(FooNestedSource source); 120 | } 121 | -------------------------------------------------------------------------------- /generator/lib/models/RefChain.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/dart/element/nullability_suffix.dart'; 3 | import 'package:analyzer/dart/element/type.dart'; 4 | 5 | // We can know lots of things from RefChain. Just like the generic type value or nullability of every field in the chain. 6 | class RefChain { 7 | final List elementList; 8 | late final bool isNullable; 9 | late final String ref; 10 | late final String refWithQuestion; 11 | 12 | factory RefChain.byPropNames(VariableElement element, Iterable names) { 13 | final properyList = _toPropertyList(element, names); 14 | return RefChain([element, ...properyList]); 15 | } 16 | 17 | RefChain(this.elementList) { 18 | // Ingore the nullability of the first element 19 | final _elementList = elementList.sublist(1); 20 | isNullable = _elementList.any((x) => checkNullable(x)); 21 | makeRef(); 22 | } 23 | 24 | makeRef() { 25 | // the first element is not nullability. 26 | if(elementList.isEmpty) { 27 | ref = ''; 28 | refWithQuestion = ''; 29 | return; 30 | } 31 | 32 | final first = elementList[0]; 33 | 34 | if(elementList.length == 1) { 35 | refWithQuestion = first.name; 36 | ref = first.name; 37 | } else { 38 | // _elementList.length >= 1, elementList.length >= 2 39 | ref = [ 40 | first.name, 41 | ..._makeVarChainList(elementList.sublist(1),"!") 42 | ].join('.'); 43 | refWithQuestion = [ 44 | first.name, 45 | ..._makeVarChainList(elementList.sublist(1, elementList.length - 1), "?"), 46 | elementList.last.name 47 | ].join('.'); 48 | } 49 | } 50 | 51 | 52 | _makeVarChainName(VariableElement element, String nullPrefix) { 53 | if(checkNullable(element)) { 54 | return "${element.name}$nullPrefix"; 55 | } else { 56 | return element.name; 57 | } 58 | } 59 | 60 | _makeVarChainList(List elementList, String nullPrefix) { 61 | return elementList.map((e) => _makeVarChainName(e, nullPrefix)); 62 | } 63 | 64 | checkNullable(VariableElement element) { 65 | return element.type.nullabilitySuffix == NullabilitySuffix.question; 66 | } 67 | 68 | from(VariableElement element) { 69 | return RefChain([element, ...elementList]); 70 | } 71 | 72 | removeLast() { 73 | return RefChain(elementList.sublist(0, elementList.length - 1)); 74 | } 75 | } 76 | 77 | 78 | // Create the property list corresponding to the nameList. 79 | List _toPropertyList(VariableElement element, Iterable nameList) { 80 | final findTargetName = nameList.first; 81 | final propList = getAllReadProperty(element); 82 | 83 | final potentielFinds = propList.where((x) => x.getDisplayString(withNullability: false).endsWith(findTargetName)); 84 | if(potentielFinds.isEmpty) { 85 | throw "Property is not found! $findTargetName"; 86 | } 87 | 88 | final targetProp = potentielFinds.first; 89 | if(nameList.length == 1) { 90 | return [targetProp]; 91 | } 92 | 93 | bool shouldContinue = targetProp.type is InterfaceType; 94 | if(shouldContinue) { 95 | return [targetProp, ..._toPropertyList(targetProp, nameList.skip(1))]; 96 | } else { 97 | throw ""; 98 | } 99 | } 100 | 101 | List getAllReadProperty(VariableElement field) { 102 | final type = field.type; 103 | if(type is! InterfaceType) { 104 | return []; 105 | } 106 | 107 | return [...type.accessors, ...type.allSupertypes.expand((e) => e.accessors)] 108 | .where((e) => e.isGetter) 109 | .map((e) => e.variable) 110 | .toList(); 111 | } -------------------------------------------------------------------------------- /generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.4.0 2 | * Extending default types to be ignored by static mapping 3 | (https://github.com/smotastic/smartstruct/issues/74) 4 | * Nullable lists are not being mapped properly. (https://github.com/smotastic/smartstruct/issues/41) 5 | * Update to analyzer 5.0.0 (https://github.com/smotastic/smartstruct/issues/78) 6 | 7 | 8 | # v1.3.0 9 | * Bump Analyzer to 4.0.0 (Thanks to @luissalgadofreire) 10 | 11 | ## Bugfixes 12 | * Fix Static Functional Mapping (https://github.com/smotastic/smartstruct/issues/68) 13 | 14 | # v1.2.7 15 | 16 | ## Bugfixes 17 | * Generator does not recognize inherited methods https://github.com/smotastic/smartstruct/issues/56 (Thanks to @skykaka) 18 | * Unable to generate files in different directories https://github.com/smotastic/smartstruct/issues/54 (Thanks to @skykaka) 19 | 20 | ## Features 21 | Static Mapping (https://github.com/smotastic/smartstruct/issues/53) 22 | Static Mapping with a proxy (https://github.com/smotastic/smartstruct/pull/59) (Thanks to @skykaka) 23 | 24 | # v1.2.6 25 | - Nested Mapping directly in the mapping annotation (https://github.com/smotastic/smartstruct/issues/26) 26 | - Better support for freezed (https://github.com/smotastic/smartstruct/issues/29) 27 | 28 | 29 | # v1.2.5 30 | - Ignore certain fields (https://github.com/smotastic/smartstruct/issues/40) 31 | 32 | # v1.2.4+1 33 | - Bugfix: Mapper failing to generate when using interfaces https://github.com/smotastic/smartstruct/issues/35 34 | 35 | 36 | # v1.2.4 37 | - Support inheritance (https://github.com/smotastic/smartstruct/pull/32) 38 | 39 | 40 | # v.1.2.3+1 41 | - Updated analyzer dependency to 2.0.0 42 | 43 | 44 | # v.1.2.3 45 | Features: 46 | - Added the functionality to add custom mapper function to a target (https://github.com/smotastic/smartstruct/pull/28) 47 | - New option caseSensitiveFields to turn on or off case sensitivity then comparing mappable fields (https://github.com/smotastic/smartstruct/pull/18) 48 | 49 | 50 | # v1.2.2 51 | Fixes: 52 | - The generator will now always run after all .g.dart files from other builders have been run, so the mapper can potentially create mappers for other created classes. See Issue [#24](https://github.com/smotastic/smartstruct/issues/24) 53 | 54 | # v.1.2.1 55 | - Constructors in the abstract mapper will now also be implemented by the generated mapper class (https://github.com/smotastic/smartstruct/issues/22). 56 | This should fix an issue where you cannot inject dependencies via the constructor in your mapper class. 57 | 58 | # v1.2.0 59 | - Fixed the useInjection attribute to properly work now (https://github.com/smotastic/smartstruct/issues/19) 60 | Note that all generated files are suffixed with *.mapper.g.dart* now instead of *.g.dart* 61 | So for migration purposes you'll have to just change the *part 'foomapper.g.dart'* in your mapper files to *part 'foomapper.mapper.g.dart* 62 | ```dart 63 | // before 64 | part 'foomapper.g.dart' 65 | // after 66 | part 'foomapper.mapper.g.dart' 67 | ``` 68 | # v1.1.3 69 | - Added Support for Lists (https://github.com/smotastic/smartstruct/issues/12) 70 | 71 | # v1.1.2+1 72 | 73 | - Added tests for the generator (https://github.com/smotastic/smartstruct/issues/8) 74 | - Added Example 75 | - Documentation 76 | 77 | # v1.1.2 78 | 79 | - Hotfix: Change Builder dependency in build.yaml 80 | 81 | # v1.1.1 82 | 83 | - Hotfix. Forgot to add smartstruct pub dev dependency 84 | 85 | # v1.1.0 86 | 87 | - Split the code into a generator, and smartstruct library project. 88 | To migrate from earlier versions, you need to add the _smartstruct_generator_ dependency to your dev_dependencies 89 | This has the advantage that your final build won't have to include the builder code, but only the mapper annotations 90 | 91 | # v1.0.5 92 | 93 | - Add support for optional source and target parameters 94 | 95 | # v.1.0.4 96 | 97 | - Add support for nested bean mapping 98 | 99 | # v1.0.3 100 | 101 | - README updated 102 | 103 | # v1.0.2 104 | 105 | - README updates 106 | 107 | # v1.0.1 108 | 109 | - Added explicit field mapping support via Mapping Annotation 110 | 111 | # v1.0.0 112 | 113 | - Initial Mapper Annotation published 114 | -------------------------------------------------------------------------------- /smartstruct/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.4.0 2 | * Extending default types to be ignored by static mapping 3 | (https://github.com/smotastic/smartstruct/issues/74) 4 | * Nullable lists are not being mapped properly. (https://github.com/smotastic/smartstruct/issues/41) 5 | * Update to analyzer 5.0.0 (https://github.com/smotastic/smartstruct/issues/78) 6 | 7 | 8 | # v1.3.0 9 | * Bump Analyzer to 4.0.0 (Thanks to @luissalgadofreire) 10 | 11 | ## Bugfixes 12 | * Fix Static Functional Mapping (https://github.com/smotastic/smartstruct/issues/68) 13 | 14 | # v1.2.7 15 | 16 | ## Bugfixes 17 | * Generator does not recognize inherited methods https://github.com/smotastic/smartstruct/issues/56 (Thanks to @skykaka) 18 | * Unable to generate files in different directories https://github.com/smotastic/smartstruct/issues/54 (Thanks to @skykaka) 19 | 20 | ## Features 21 | Static Mapping (https://github.com/smotastic/smartstruct/issues/53) 22 | Static Mapping with a proxy (https://github.com/smotastic/smartstruct/pull/59) (Thanks to @skykaka) 23 | 24 | # v1.2.6 25 | - Nested Mapping directly in the mapping annotation (https://github.com/smotastic/smartstruct/issues/26) 26 | - Better support for freezed (https://github.com/smotastic/smartstruct/issues/29) 27 | 28 | 29 | # v1.2.5 30 | - Ignore certain fields (https://github.com/smotastic/smartstruct/issues/40) 31 | 32 | # v1.2.4+1 33 | - Bugfix: Mapper failing to generate when using interfaces https://github.com/smotastic/smartstruct/issues/35 34 | 35 | 36 | # v1.2.4 37 | - Support inheritance (https://github.com/smotastic/smartstruct/pull/32) 38 | 39 | 40 | # v.1.2.3+1 41 | - Updated analyzer dependency to 2.0.0 42 | 43 | 44 | # v.1.2.3 45 | Features: 46 | - Added the functionality to add custom mapper function to a target (https://github.com/smotastic/smartstruct/pull/28) 47 | - New option caseSensitiveFields to turn on or off case sensitivity then comparing mappable fields (https://github.com/smotastic/smartstruct/pull/18) 48 | 49 | 50 | # v1.2.2 51 | Fixes: 52 | - The generator will now always run after all .g.dart files from other builders have been run, so the mapper can potentially create mappers for other created classes. See Issue [#24](https://github.com/smotastic/smartstruct/issues/24) 53 | 54 | # v.1.2.1 55 | - Constructors in the abstract mapper will now also be implemented by the generated mapper class (https://github.com/smotastic/smartstruct/issues/22). 56 | This should fix an issue where you cannot inject dependencies via the constructor in your mapper class. 57 | 58 | # v1.2.0 59 | - Fixed the useInjection attribute to properly work now (https://github.com/smotastic/smartstruct/issues/19) 60 | Note that all generated files are suffixed with *.mapper.g.dart* now instead of *.g.dart* 61 | So for migration purposes you'll have to just change the *part 'foomapper.g.dart'* in your mapper files to *part 'foomapper.mapper.g.dart* 62 | ```dart 63 | // before 64 | part 'foomapper.g.dart' 65 | // after 66 | part 'foomapper.mapper.g.dart' 67 | ``` 68 | # v1.1.3 69 | - Added Support for Lists (https://github.com/smotastic/smartstruct/issues/12) 70 | 71 | # v1.1.2+1 72 | 73 | - Added tests for the generator (https://github.com/smotastic/smartstruct/issues/8) 74 | - Added Example 75 | - Documentation 76 | 77 | # v1.1.2 78 | 79 | - Hotfix: Change Builder dependency in build.yaml 80 | 81 | # v1.1.1 82 | 83 | - Hotfix. Forgot to add smartstruct pub dev dependency 84 | 85 | # v1.1.0 86 | 87 | - Split the code into a generator, and smartstruct library project. 88 | To migrate from earlier versions, you need to add the _smartstruct_generator_ dependency to your dev_dependencies 89 | This has the advantage that your final build won't have to include the builder code, but only the mapper annotations 90 | 91 | # v1.0.5 92 | 93 | - Add support for optional source and target parameters 94 | 95 | # v.1.0.4 96 | 97 | - Add support for nested bean mapping 98 | 99 | # v1.0.3 100 | 101 | - README updated 102 | 103 | # v1.0.2 104 | 105 | - README updates 106 | 107 | # v1.0.1 108 | 109 | - Added explicit field mapping support via Mapping Annotation 110 | 111 | # v1.0.0 112 | 113 | - Initial Mapper Annotation published 114 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /generator/lib/code_builders/class_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:analyzer/dart/element/element.dart'; 4 | import 'package:analyzer/dart/element/type.dart'; 5 | import 'package:code_builder/code_builder.dart'; 6 | import 'package:smartstruct_generator/code_builders/method_builder.dart'; 7 | import 'package:smartstruct_generator/code_builders/static_proxy_builder.dart'; 8 | import 'package:smartstruct_generator/mapper_config.dart'; 9 | 10 | import 'parameter_copy.dart'; 11 | 12 | Library buildMapperClass( 13 | ClassElement abstractClass, Map config) { 14 | return Library((b) => b.body.addAll( 15 | [ 16 | _generateMapperImplementationClass(abstractClass, config), 17 | ..._generateStaticMethods(abstractClass, config), 18 | ..._generateStaticProxy(abstractClass, config), 19 | ], 20 | )); 21 | } 22 | 23 | List _generateStaticProxy( 24 | ClassElement abstractClass, Map config) { 25 | if (config['generateStaticProxy']) { 26 | return [generateStaticProxy(abstractClass)]; 27 | } 28 | return []; 29 | } 30 | 31 | List _generateStaticMethods( 32 | ClassElement abstractClass, Map config) { 33 | var staticMethods = abstractClass.methods 34 | .where( 35 | (method) => 36 | method.isStatic && 37 | _shouldGenerateStaticMethod(method) && 38 | !_shouldNotBeGenerated(method.returnType) && 39 | !_isAbstractType(method.returnType), 40 | ) 41 | .map((method) => 42 | buildStaticMapperImplementation(config, method, abstractClass)) 43 | .toList(); 44 | return staticMethods; 45 | } 46 | 47 | bool _shouldGenerateStaticMethod(MethodElement method) { 48 | return !MapperConfig.isIgnoreMapping(method); 49 | } 50 | 51 | bool _isAbstractType(DartType type) { 52 | final element = type.element2; 53 | if (element is! ClassElement) { 54 | return false; 55 | } 56 | return element.isAbstract; 57 | } 58 | 59 | bool _shouldNotBeGenerated(DartType type) { 60 | return type.isDartCoreBool || 61 | type.isDartCoreDouble || 62 | type.isDartCoreInt || 63 | type.isDartCoreNum || 64 | type.isDartCoreString || 65 | (type is InterfaceType && true == type.superclass?.isDartCoreEnum) || 66 | type.isDartCoreList || 67 | type.isDartCoreSet || 68 | type.isDartCoreMap; 69 | } 70 | 71 | Class _generateMapperImplementationClass( 72 | ClassElement abstractClass, Map config) { 73 | return Class( 74 | (b) => b 75 | ..annotations.addAll(_generateClassAnnotations(config, abstractClass)) 76 | ..name = '${abstractClass.displayName}Impl' 77 | ..constructors.addAll( 78 | abstractClass.constructors.map((c) => _generateConstructor(c))) 79 | ..extend = refer(abstractClass.displayName) 80 | ..methods.addAll(_getAllMethods(abstractClass.thisType) 81 | .where((method) => method.isAbstract) 82 | .map((method) => 83 | buildMapperImplementation(config, method, abstractClass))), 84 | ); 85 | } 86 | 87 | List _getAllMethods(InterfaceType interfaceType) { 88 | final set = LinkedHashSet( 89 | equals: (p0, p1) => p0.name == p1.name, 90 | hashCode: (p0) => p0.name.hashCode, 91 | ); 92 | // The methods of subclass has the priority. 93 | // So it should be added before the methods of superclass. 94 | set.addAll(interfaceType.methods); 95 | set.addAll(interfaceType.allSupertypes.expand(_getAllMethods)); 96 | return set.toList(); 97 | } 98 | 99 | /// Generates a [Constructor] by copying the given [ConstructorElement] c 100 | Constructor _generateConstructor(ConstructorElement c) { 101 | final builder = ConstructorBuilder(); 102 | if (c.name.isNotEmpty) { 103 | builder.name = c.name; 104 | } 105 | 106 | final namedParams = c.parameters 107 | .where((element) => element.isNamed) 108 | .map((e) => copyParameter(e)); 109 | 110 | final positionalParams = c.parameters 111 | .where((element) => element.isPositional) 112 | .map((e) => copyParameter(e)); 113 | 114 | builder.optionalParameters.addAll(namedParams); 115 | builder.requiredParameters.addAll(positionalParams); 116 | 117 | final namedArgs = { 118 | for (var f in c.parameters.where((element) => element.isNamed)) 119 | f.name: refer(f.name) 120 | }; 121 | 122 | var positionalArgs = c.parameters 123 | .where((element) => element.isPositional) 124 | .map((e) => refer(e.name)); 125 | 126 | Expression superCall = refer('super'); 127 | if (c.name.isNotEmpty) { 128 | superCall = superCall.property(c.name); 129 | } 130 | builder.initializers.add(superCall.call(positionalArgs, namedArgs).code); 131 | 132 | return builder.build(); 133 | } 134 | 135 | /// Generates the Class Annotations for the created mapper implementation 136 | /// 137 | /// If the config contains the useInjection key, a [LazySingleton] Annotation will be added to the resulting implementation of the mapper. 138 | /// Note that the mapper interface class has to import the injectable library if this is the case. 139 | Iterable _generateClassAnnotations( 140 | Map config, ClassElement classElement) { 141 | final ret = []; 142 | 143 | if (config['useInjection']) { 144 | ret.add(refer('LazySingleton') 145 | .newInstance([], {'as': refer(classElement.displayName)})); 146 | } 147 | return ret; 148 | } 149 | -------------------------------------------------------------------------------- /smartstruct/example/example.md: -------------------------------------------------------------------------------- 1 | # Simple Mapping 2 | 3 | ```dart 4 | // dogmapper.dart 5 | class Dog { 6 | final String breed; 7 | final int age; 8 | final String name; 9 | Dog(this.breed, this.age, this.name); 10 | } 11 | ``` 12 | 13 | ```dart 14 | class DogModel { 15 | final String breed; 16 | final int age; 17 | final String name; 18 | DogModel(this.breed, this.age, this.name); 19 | } 20 | ``` 21 | 22 | To generate a mapper for these two beans, you need to create a mapper interface. 23 | 24 | ```dart 25 | import 'package:smartstruct/smartstruct.dart'; 26 | 27 | // dog.mapper.dart 28 | part 'dogmapper.mapper.g.dart'; 29 | 30 | @Mapper() 31 | abstract class DogMapper { 32 | Dog fromModel(DogModel model); 33 | } 34 | ``` 35 | 36 | # Explicit Field Mapping 37 | 38 | 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. 39 | 40 | ```dart 41 | class Dog { 42 | final String name; 43 | Dog(this.name); 44 | } 45 | class DogModel { 46 | final String dogName; 47 | DogModel(this.dogName); 48 | } 49 | ``` 50 | 51 | ```dart 52 | // dogmapper.dart 53 | import 'package:smartstruct/smartstruct.dart'; 54 | 55 | part 'dogmapper.mapper.g.dart'; 56 | 57 | @Mapper() 58 | class DogMapper { 59 | @Mapping(source: 'dogName', target: 'name') 60 | Dog fromModel(DogModel model); 61 | } 62 | ``` 63 | 64 | In this case, the field _dogName_ of _DogModel_ will be mapped to the field _name_ of the resulting _Dog_ 65 | 66 | ```dart 67 | // dogmapper.mapper.g.dart 68 | 69 | class DogMapperImpl extends DogMapper { 70 | @override 71 | Dog fromModel(DogModel model) { 72 | Dog dog = Dog(model.dogName); 73 | return dog; 74 | } 75 | } 76 | ``` 77 | 78 | ## Function Mapping 79 | 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. 80 | ```dart 81 | class Dog { 82 | final String name; 83 | final String breed; 84 | Dog(this.name, this.breed); 85 | } 86 | class DogModel { 87 | final String name; 88 | DogModel(this.name); 89 | } 90 | ``` 91 | 92 | ```dart 93 | @Mapper() 94 | class DogMapper { 95 | static String randomBreed(DogModel model) => 'some random breed'; 96 | 97 | @Mapping(source: randomBreed, target: 'breed') 98 | Dog fromModel(DogModel model); 99 | } 100 | ``` 101 | 102 | Will generate the following Mapper. 103 | ```dart 104 | class DogMapperImpl extends DogMapper { 105 | @override 106 | Dog fromModel(DogModel model) { 107 | Dog dog = Dog(model.dogName, DogMapper.randomBreed(model)); 108 | return dog; 109 | } 110 | } 111 | ``` 112 | 113 | # Nested Bean Mapping 114 | 115 | Nested beans can be mapped, by defining an additional mapper method for the nested bean. 116 | 117 | ```dart 118 | class NestedTarget { 119 | final SubNestedTarget subNested; 120 | NestedTarget(this.subNested); 121 | } 122 | class SubNestedTarget { 123 | final String myProperty; 124 | SubNestedTarget(this.myProperty); 125 | } 126 | 127 | class NestedSource { 128 | final SubNestedSource subNested; 129 | NestedSource(this.subNested); 130 | } 131 | 132 | class SubNestedSource { 133 | final String myProperty; 134 | SubNestedSource(this.myProperty); 135 | } 136 | ``` 137 | 138 | ```dart 139 | // nestedmapper.dart 140 | import 'package:smartstruct/smartstruct.dart'; 141 | 142 | part 'nested.mapper.g.dart'; 143 | 144 | @Mapper() 145 | abstract class NestedMapper { 146 | NestedTarget fromModel(NestedSource model); 147 | 148 | SubNestedTarget fromSubClassModel(SubNestedSource model); 149 | } 150 | ``` 151 | 152 | Will generate the mapper 153 | 154 | ```dart 155 | // nestedmapper.mapper.g.dart 156 | class NestedMapperImpl extends NestedMapper { 157 | @override 158 | NestedTarget fromModel(NestedSource model) { 159 | final nestedtarget = NestedTarget(fromSubClassModel(model.subNested)); 160 | return nestedtarget; 161 | } 162 | 163 | @override 164 | SubNestedTarget fromSubClassModel(SubNestedSource model) { 165 | final subnestedtarget = SubNestedTarget(model.myProperty); 166 | return subnestedtarget; 167 | } 168 | } 169 | 170 | ``` 171 | 172 | 173 | # List Support 174 | Lists will be mapped as new instances of a list, with help of the map method. 175 | ```dart 176 | class Source { 177 | final List intList; 178 | final List entryList; 179 | 180 | Source(this.intList, this.entryList); 181 | } 182 | 183 | class SourceEntry { 184 | final String prop; 185 | 186 | SourceEntry(this.prop); 187 | } 188 | 189 | class Target { 190 | final List intList; 191 | final List entryList; 192 | 193 | Target(this.intList, this.entryList); 194 | } 195 | 196 | class TargetEntry { 197 | final String prop; 198 | 199 | TargetEntry(this.prop); 200 | } 201 | 202 | @Mapper() 203 | abstract class ListMapper { 204 | Target fromSource(Source source); 205 | TargetEntry fromSourceEntry(SourceEntry source); 206 | } 207 | ``` 208 | Will generate the Mapper 209 | 210 | ```dart 211 | class ListMapperImpl extends ListMapper { 212 | @override 213 | Target fromSource(Source source) { 214 | final target = Target( 215 | source.intList.map((e) => e).toList(), 216 | source.entryList.map(fromSourceEntry).toList()); 217 | return target; 218 | } 219 | 220 | @override 221 | TargetEntry fromSourceEntry(SourceEntry source) { 222 | final targetentry = TargetEntry(source.prop); 223 | return targetentry; 224 | } 225 | } 226 | ``` 227 | 228 | # Injectable 229 | 230 | The Mapper can be made a lazy injectable singleton, by setting the argument _useInjection_ to true, in the Mapper Interface. 231 | In this case you also need to add the injectable dependency, as described here. https://pub.dev/packages/injectable 232 | 233 | Make sure, that in the Mapper File, you import the injectable dependency, before running the build_runner! 234 | 235 | ```dart 236 | // dogmapper.dart 237 | 238 | import 'package:smartstruct/smartstruct.dart'; 239 | import 'package:injectable/injectable.dart'; 240 | 241 | part 'dogmapper.mapper.g.dart'; 242 | 243 | @Mapper(useInjectable = true) 244 | abstract class DogMapper { 245 | Dog fromModel(DogModel model); 246 | } 247 | ``` 248 | 249 | ```dart 250 | // dogmapper.mapper.g.dart 251 | @LazySingleton(as: DogMapper) 252 | class DogMapperImpl extends DogMapper {...} 253 | ``` 254 | -------------------------------------------------------------------------------- /generator/lib/code_builders/assignment_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/dart/element/nullability_suffix.dart'; 3 | import 'package:analyzer/dart/element/type.dart'; 4 | import 'package:code_builder/code_builder.dart'; 5 | import 'package:smartstruct_generator/models/source_assignment.dart'; 6 | 7 | /// Generates an assignment of a reference to a sourcefield. 8 | /// 9 | /// The assignment is the property {sourceField} of the given [Reference] {sourceReference}. 10 | /// If a method in the given [ClassElement] exists, 11 | /// whose returntype matches the type of the targetField, and its first parameter type matches the sourceField type, 12 | /// then this method will be used for the assignment instead, and passing the sourceField property of the given [Reference] as the argument of the method. 13 | /// 14 | /// Returns the Expression of the sourceField assignment, such as: 15 | /// ```dart 16 | /// model.id; 17 | /// fromSub(model.sub); 18 | /// ``` 19 | /// 20 | Expression generateSourceFieldAssignment(SourceAssignment sourceAssignment, 21 | ClassElement abstractMapper, VariableElement targetField) { 22 | Expression sourceFieldAssignment; 23 | 24 | if (sourceAssignment.shouldUseFunction()) { 25 | final sourceFunction = sourceAssignment.function!; 26 | final references = sourceAssignment.params! 27 | .map((sourceParam) => refer(sourceParam.displayName)); 28 | Expression expr = refer(sourceFunction.name); 29 | if (sourceFunction.isStatic && 30 | sourceFunction.enclosingElement3.name != null) { 31 | expr = refer(sourceFunction.enclosingElement3.name!) 32 | .property(sourceFunction.name); 33 | } 34 | sourceFieldAssignment = expr.call( 35 | [...references], makeNamedArgumentForStaticFunction(sourceFunction)); 36 | 37 | // The return of the function may be needed a nested mapping. 38 | sourceFieldAssignment = invokeNestedMappingForStaticFunction( 39 | sourceFunction, abstractMapper, targetField, sourceFieldAssignment); 40 | } else { 41 | // final sourceClass = sourceAssignment.sourceClass!; 42 | final sourceField = sourceAssignment.field!; 43 | final sourceReference = refer(sourceAssignment.sourceName!); 44 | sourceFieldAssignment = sourceReference.property(sourceField.name); 45 | // list support 46 | if (sourceAssignment.shouldAssignList(targetField.type)) { 47 | final sourceListType = _getGenericTypes(sourceField.type).first; 48 | final targetListType = _getGenericTypes(targetField.type).first; 49 | final matchingMappingListMethods = _findMatchingMappingMethod( 50 | abstractMapper, targetListType, sourceListType); 51 | 52 | // mapping expression, default is just the identity, 53 | // for example for primitive types or objects that do not have their own mapping method 54 | var expr = refer('(e) => e'); 55 | var sourceIsNullable = 56 | sourceListType.nullabilitySuffix == NullabilitySuffix.question; 57 | var targetIsNullable = 58 | targetListType.nullabilitySuffix == NullabilitySuffix.question; 59 | 60 | var needTargetFilter = sourceIsNullable && !targetIsNullable; 61 | if (matchingMappingListMethods.isNotEmpty) { 62 | final nestedMapping = matchingMappingListMethods.first; 63 | // expr = refer(nestedMapping.name); 64 | final invokeStr = invokeNestedMappingFunction( 65 | nestedMapping, 66 | sourceIsNullable, 67 | refer("x"), 68 | refer("x"), 69 | ).accept(DartEmitter()).toString(); 70 | expr = refer(''' 71 | (x) => $invokeStr 72 | '''); 73 | final returnIsNullable = 74 | checkNestMappingReturnNullable(nestedMapping, sourceIsNullable); 75 | needTargetFilter = !targetIsNullable && returnIsNullable; 76 | } 77 | 78 | if (sourceField.type.nullabilitySuffix == NullabilitySuffix.question) { 79 | sourceFieldAssignment = 80 | // source.{field}.map 81 | sourceReference.property(sourceField.name).nullSafeProperty('map') 82 | // (expr) 83 | .call([expr]); 84 | } else { 85 | sourceFieldAssignment = 86 | // source.{field}.map 87 | sourceReference.property(sourceField.name).property('map') 88 | // (expr) 89 | .call([expr]); 90 | } 91 | 92 | if (needTargetFilter) { 93 | sourceFieldAssignment = sourceFieldAssignment 94 | .property("where") 95 | .call([refer("(x) => x != null")]); 96 | } 97 | 98 | if (sourceAssignment.needCollect(targetField.type)) { 99 | sourceFieldAssignment = sourceFieldAssignment 100 | //.toList() .toSet() 101 | .property(sourceAssignment.collectInvoke(targetField.type)) 102 | // .property('toList') 103 | // .call([]) 104 | ; 105 | 106 | if (targetField.type.nullabilitySuffix == NullabilitySuffix.none && 107 | sourceField.type.nullabilitySuffix == NullabilitySuffix.question) { 108 | sourceFieldAssignment = sourceFieldAssignment.ifNullThen(refer('[]')); 109 | } 110 | } 111 | 112 | if (needTargetFilter) { 113 | sourceFieldAssignment = sourceFieldAssignment.asA( 114 | refer(targetField.type.getDisplayString(withNullability: true))); 115 | } 116 | } else { 117 | // found a mapping method in the class which will map the source to target 118 | final matchingMappingMethods = _findMatchingMappingMethod( 119 | abstractMapper, targetField.type, sourceField.type); 120 | 121 | // nested classes can be mapped with their own mapping methods 122 | if (matchingMappingMethods.isNotEmpty) { 123 | sourceFieldAssignment = invokeNestedMappingFunction( 124 | matchingMappingMethods.first, 125 | sourceAssignment.refChain!.isNullable, 126 | refer(sourceAssignment.refChain!.refWithQuestion), 127 | refer(sourceAssignment.refChain!.ref), 128 | ); 129 | } 130 | } 131 | } 132 | return sourceFieldAssignment; 133 | } 134 | 135 | Expression invokeNestedMappingFunction( 136 | MethodElement method, 137 | bool sourceNullable, 138 | Expression refWithQuestion, 139 | Expression ref, 140 | ) { 141 | Expression sourceFieldAssignment; 142 | if (method.parameters.first.isOptional) { 143 | // The parameter can be null. 144 | sourceFieldAssignment = refer(method.name).call([refWithQuestion]); 145 | } else { 146 | sourceFieldAssignment = refer(method.name).call([ref]); 147 | sourceFieldAssignment = checkNullExpression( 148 | sourceNullable, refWithQuestion, sourceFieldAssignment); 149 | } 150 | return sourceFieldAssignment; 151 | } 152 | 153 | Expression invokeNestedMappingForStaticFunction( 154 | ExecutableElement sourceFunction, 155 | ClassElement abstractMapper, 156 | VariableElement targetField, 157 | Expression sourceFieldAssignment, 158 | ) { 159 | final returnType = sourceFunction.returnType; 160 | final matchingMappingMethods = 161 | _findMatchingMappingMethod(abstractMapper, targetField.type, returnType); 162 | if (matchingMappingMethods.isNotEmpty) { 163 | final nestedMappingMethod = matchingMappingMethods.first; 164 | 165 | if (nestedMappingMethod.parameters.first.type.nullabilitySuffix != 166 | NullabilitySuffix.question && 167 | sourceFunction.returnType.nullabilitySuffix == 168 | NullabilitySuffix.question) { 169 | final str = makeNullCheckCall( 170 | sourceFieldAssignment.accept(DartEmitter()).toString(), 171 | nestedMappingMethod, 172 | ); 173 | sourceFieldAssignment = refer(str); 174 | } else { 175 | sourceFieldAssignment = refer(matchingMappingMethods.first.name) 176 | .call([sourceFieldAssignment]); 177 | } 178 | } 179 | return sourceFieldAssignment; 180 | } 181 | 182 | /// Finds a matching Mapping Method in [classElement] 183 | /// which has the same return type as the given [targetReturnType] and same parametertype as the given [sourceParameterType] 184 | Iterable _findMatchingMappingMethod(ClassElement classElement, 185 | DartType targetReturnType, DartType sourceParameterType) { 186 | final matchingMappingMethods = classElement.methods.where((met) { 187 | // Sometimes the user is troubled by the nullability of these types. 188 | // So ingore the nullability of all the type for the nested mapping function is more easy to be matched. 189 | // The process of nullability is one duty for this library. 190 | 191 | if (met.parameters.isEmpty) { 192 | return false; 193 | } 194 | final metReturnElement = met.returnType.element2; 195 | final metParameterElement = met.parameters.first.type.element2; 196 | 197 | final targetReturnElement = targetReturnType.element2; 198 | final srcParameterElement = sourceParameterType.element2; 199 | 200 | return metReturnElement == targetReturnElement && 201 | (metParameterElement == srcParameterElement); 202 | 203 | // return met.returnType == targetReturnType && 204 | // met.parameters.isNotEmpty && met.parameters.first.type == sourceParameterType; 205 | }); 206 | return matchingMappingMethods; 207 | } 208 | 209 | Iterable _getGenericTypes(DartType type) { 210 | return type is ParameterizedType ? type.typeArguments : const []; 211 | } 212 | 213 | // Sometimes we need to pass some variable to the static function just like "this pointer". 214 | // We can use the named parameters to implement the goal. 215 | Map makeNamedArgumentForStaticFunction( 216 | ExecutableElement element) { 217 | final argumentMap = { 218 | "mapper": "this", 219 | "\$this": "this", 220 | }; 221 | final namedParameterList = element.parameters 222 | .where((p) => p.isNamed && argumentMap.containsKey(p.name)) 223 | .toList(); 224 | 225 | return namedParameterList.asMap().map( 226 | (key, value) => MapEntry(value.name, refer(argumentMap[value.name]!))); 227 | } 228 | 229 | Expression checkNullExpression( 230 | bool needCheck, 231 | Expression sourceRef, 232 | Expression expression, 233 | ) { 234 | if (needCheck) { 235 | return sourceRef.equalTo(literalNull).conditional( 236 | literalNull, 237 | expression, 238 | ); 239 | } else { 240 | return expression; 241 | } 242 | } 243 | 244 | makeNullCheckCall( 245 | String checkTarget, 246 | MethodElement method, 247 | ) { 248 | final methodInvoke = 249 | refer(method.name).call([refer("tmp")]).accept(DartEmitter()).toString(); 250 | return ''' 251 | (){ 252 | final tmp = $checkTarget; 253 | return tmp == null ? null : $methodInvoke; 254 | }() 255 | '''; 256 | } 257 | 258 | checkNestMappingReturnNullable(MethodElement method, bool inputNullable) { 259 | final returnIsNullable = (inputNullable && 260 | method.parameters.first.type.nullabilitySuffix != 261 | NullabilitySuffix.question) || 262 | (inputNullable && 263 | method.returnType.nullabilitySuffix == NullabilitySuffix.question); 264 | return returnIsNullable; 265 | } 266 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "47.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "4.7.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.1" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.7.0" 32 | build: 33 | dependency: transitive 34 | description: 35 | name: build 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.3.1" 39 | build_config: 40 | dependency: transitive 41 | description: 42 | name: build_config 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.0" 46 | build_daemon: 47 | dependency: transitive 48 | description: 49 | name: build_daemon 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "3.1.0" 53 | build_resolvers: 54 | dependency: transitive 55 | description: 56 | name: build_resolvers 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.10" 60 | build_runner: 61 | dependency: "direct dev" 62 | description: 63 | name: build_runner 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.11" 67 | build_runner_core: 68 | dependency: transitive 69 | description: 70 | name: build_runner_core 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "7.2.2" 74 | built_collection: 75 | dependency: transitive 76 | description: 77 | name: built_collection 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "5.0.0" 81 | built_value: 82 | dependency: transitive 83 | description: 84 | name: built_value 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "8.4.1" 88 | charcode: 89 | dependency: transitive 90 | description: 91 | name: charcode 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.2.0" 95 | checked_yaml: 96 | dependency: transitive 97 | description: 98 | name: checked_yaml 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.0.1" 102 | code_builder: 103 | dependency: transitive 104 | description: 105 | name: code_builder 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "4.1.0" 109 | collection: 110 | dependency: transitive 111 | description: 112 | name: collection 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.15.0" 116 | convert: 117 | dependency: transitive 118 | description: 119 | name: convert 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "3.0.0" 123 | crypto: 124 | dependency: transitive 125 | description: 126 | name: crypto 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "3.0.1" 130 | dart_style: 131 | dependency: transitive 132 | description: 133 | name: dart_style 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "2.2.4" 137 | file: 138 | dependency: transitive 139 | description: 140 | name: file 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "6.1.1" 144 | fixnum: 145 | dependency: transitive 146 | description: 147 | name: fixnum 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "1.0.0" 151 | freezed: 152 | dependency: "direct dev" 153 | description: 154 | name: freezed 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.1.0+1" 158 | freezed_annotation: 159 | dependency: "direct main" 160 | description: 161 | name: freezed_annotation 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.1.0" 165 | frontend_server_client: 166 | dependency: transitive 167 | description: 168 | name: frontend_server_client 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "2.1.0" 172 | get_it: 173 | dependency: transitive 174 | description: 175 | name: get_it 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "7.2.0" 179 | glob: 180 | dependency: transitive 181 | description: 182 | name: glob 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.0.1" 186 | graphs: 187 | dependency: transitive 188 | description: 189 | name: graphs 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "2.0.0" 193 | http_multi_server: 194 | dependency: transitive 195 | description: 196 | name: http_multi_server 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "3.0.1" 200 | http_parser: 201 | dependency: transitive 202 | description: 203 | name: http_parser 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "4.0.0" 207 | injectable: 208 | dependency: "direct main" 209 | description: 210 | name: injectable 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.5.3" 214 | injectable_generator: 215 | dependency: "direct dev" 216 | description: 217 | name: injectable_generator 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.5.4" 221 | io: 222 | dependency: transitive 223 | description: 224 | name: io 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "1.0.0" 228 | js: 229 | dependency: transitive 230 | description: 231 | name: js 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "0.6.3" 235 | json_annotation: 236 | dependency: transitive 237 | description: 238 | name: json_annotation 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "4.4.0" 242 | logging: 243 | dependency: transitive 244 | description: 245 | name: logging 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "1.0.1" 249 | matcher: 250 | dependency: transitive 251 | description: 252 | name: matcher 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "0.12.10" 256 | meta: 257 | dependency: transitive 258 | description: 259 | name: meta 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.7.0" 263 | mime: 264 | dependency: transitive 265 | description: 266 | name: mime 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.0.0" 270 | package_config: 271 | dependency: transitive 272 | description: 273 | name: package_config 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "2.0.0" 277 | path: 278 | dependency: transitive 279 | description: 280 | name: path 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.8.1" 284 | pedantic: 285 | dependency: transitive 286 | description: 287 | name: pedantic 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.11.0" 291 | pool: 292 | dependency: transitive 293 | description: 294 | name: pool 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.5.0" 298 | pub_semver: 299 | dependency: transitive 300 | description: 301 | name: pub_semver 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "2.0.0" 305 | pubspec_parse: 306 | dependency: transitive 307 | description: 308 | name: pubspec_parse 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.0.0" 312 | shelf: 313 | dependency: transitive 314 | description: 315 | name: shelf 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.1.4" 319 | shelf_web_socket: 320 | dependency: transitive 321 | description: 322 | name: shelf_web_socket 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "1.0.1" 326 | smartstruct: 327 | dependency: "direct main" 328 | description: 329 | name: smartstruct 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "1.3.0" 333 | smartstruct_generator: 334 | dependency: "direct dev" 335 | description: 336 | name: smartstruct_generator 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.3.0" 340 | source_gen: 341 | dependency: transitive 342 | description: 343 | name: source_gen 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "1.2.5" 347 | source_span: 348 | dependency: transitive 349 | description: 350 | name: source_span 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "1.8.1" 354 | stack_trace: 355 | dependency: transitive 356 | description: 357 | name: stack_trace 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "1.10.0" 361 | stream_channel: 362 | dependency: transitive 363 | description: 364 | name: stream_channel 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "2.1.0" 368 | stream_transform: 369 | dependency: transitive 370 | description: 371 | name: stream_transform 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "2.0.0" 375 | string_scanner: 376 | dependency: transitive 377 | description: 378 | name: string_scanner 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "1.1.0" 382 | term_glyph: 383 | dependency: transitive 384 | description: 385 | name: term_glyph 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "1.2.0" 389 | timing: 390 | dependency: transitive 391 | description: 392 | name: timing 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "1.0.0" 396 | typed_data: 397 | dependency: transitive 398 | description: 399 | name: typed_data 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "1.3.0" 403 | watcher: 404 | dependency: transitive 405 | description: 406 | name: watcher 407 | url: "https://pub.dartlang.org" 408 | source: hosted 409 | version: "1.0.0" 410 | web_socket_channel: 411 | dependency: transitive 412 | description: 413 | name: web_socket_channel 414 | url: "https://pub.dartlang.org" 415 | source: hosted 416 | version: "2.1.0" 417 | yaml: 418 | dependency: transitive 419 | description: 420 | name: yaml 421 | url: "https://pub.dartlang.org" 422 | source: hosted 423 | version: "3.1.0" 424 | sdks: 425 | dart: ">=2.17.0 <3.0.0" 426 | -------------------------------------------------------------------------------- /generator/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "52.0.0" 11 | analyzer: 12 | dependency: "direct main" 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "5.4.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.3.1" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.9.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | build: 40 | dependency: "direct main" 41 | description: 42 | name: build 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.3.1" 46 | build_config: 47 | dependency: transitive 48 | description: 49 | name: build_config 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.0" 53 | build_resolvers: 54 | dependency: transitive 55 | description: 56 | name: build_resolvers 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.10" 60 | build_test: 61 | dependency: transitive 62 | description: 63 | name: build_test 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.5" 67 | built_collection: 68 | dependency: transitive 69 | description: 70 | name: built_collection 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "5.1.1" 74 | built_value: 75 | dependency: transitive 76 | description: 77 | name: built_value 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "8.4.1" 81 | checked_yaml: 82 | dependency: transitive 83 | description: 84 | name: checked_yaml 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.0.1" 88 | code_builder: 89 | dependency: "direct main" 90 | description: 91 | name: code_builder 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "4.4.0" 95 | collection: 96 | dependency: transitive 97 | description: 98 | name: collection 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.17.1" 102 | convert: 103 | dependency: transitive 104 | description: 105 | name: convert 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "3.0.2" 109 | coverage: 110 | dependency: transitive 111 | description: 112 | name: coverage 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.6.1" 116 | crypto: 117 | dependency: transitive 118 | description: 119 | name: crypto 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "3.0.2" 123 | csslib: 124 | dependency: transitive 125 | description: 126 | name: csslib 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "0.17.2" 130 | dart_style: 131 | dependency: transitive 132 | description: 133 | name: dart_style 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "2.2.4" 137 | file: 138 | dependency: transitive 139 | description: 140 | name: file 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "6.1.4" 144 | fixnum: 145 | dependency: transitive 146 | description: 147 | name: fixnum 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "1.0.1" 151 | frontend_server_client: 152 | dependency: transitive 153 | description: 154 | name: frontend_server_client 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.1.3" 158 | glob: 159 | dependency: transitive 160 | description: 161 | name: glob 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.1.0" 165 | graphs: 166 | dependency: transitive 167 | description: 168 | name: graphs 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "2.1.0" 172 | html: 173 | dependency: transitive 174 | description: 175 | name: html 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "0.15.0" 179 | http_multi_server: 180 | dependency: transitive 181 | description: 182 | name: http_multi_server 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "3.2.1" 186 | http_parser: 187 | dependency: transitive 188 | description: 189 | name: http_parser 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "4.0.1" 193 | io: 194 | dependency: transitive 195 | description: 196 | name: io 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.0.3" 200 | js: 201 | dependency: transitive 202 | description: 203 | name: js 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.6.4" 207 | json_annotation: 208 | dependency: transitive 209 | description: 210 | name: json_annotation 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "4.7.0" 214 | lints: 215 | dependency: "direct dev" 216 | description: 217 | name: lints 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.0.1" 221 | logging: 222 | dependency: transitive 223 | description: 224 | name: logging 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "1.1.0" 228 | matcher: 229 | dependency: transitive 230 | description: 231 | name: matcher 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "0.12.12" 235 | meta: 236 | dependency: transitive 237 | description: 238 | name: meta 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.8.0" 242 | mime: 243 | dependency: transitive 244 | description: 245 | name: mime 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "1.0.2" 249 | node_preamble: 250 | dependency: transitive 251 | description: 252 | name: node_preamble 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "2.0.1" 256 | package_config: 257 | dependency: transitive 258 | description: 259 | name: package_config 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "2.1.0" 263 | path: 264 | dependency: "direct main" 265 | description: 266 | name: path 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.8.2" 270 | pool: 271 | dependency: transitive 272 | description: 273 | name: pool 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "1.5.1" 277 | pub_semver: 278 | dependency: transitive 279 | description: 280 | name: pub_semver 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "2.1.1" 284 | pubspec_parse: 285 | dependency: transitive 286 | description: 287 | name: pubspec_parse 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.2.1" 291 | shelf: 292 | dependency: transitive 293 | description: 294 | name: shelf 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.4.0" 298 | shelf_packages_handler: 299 | dependency: transitive 300 | description: 301 | name: shelf_packages_handler 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "3.0.1" 305 | shelf_static: 306 | dependency: transitive 307 | description: 308 | name: shelf_static 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.1.1" 312 | shelf_web_socket: 313 | dependency: transitive 314 | description: 315 | name: shelf_web_socket 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.0.2" 319 | smartstruct: 320 | dependency: "direct main" 321 | description: 322 | name: smartstruct 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "1.4.0" 326 | source_gen: 327 | dependency: "direct main" 328 | description: 329 | name: source_gen 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "1.2.5" 333 | source_gen_test: 334 | dependency: "direct dev" 335 | description: 336 | name: source_gen_test 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.0.4" 340 | source_map_stack_trace: 341 | dependency: transitive 342 | description: 343 | name: source_map_stack_trace 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "2.1.0" 347 | source_maps: 348 | dependency: transitive 349 | description: 350 | name: source_maps 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "0.10.10" 354 | source_span: 355 | dependency: transitive 356 | description: 357 | name: source_span 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "1.9.1" 361 | stack_trace: 362 | dependency: transitive 363 | description: 364 | name: stack_trace 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "1.10.0" 368 | stream_channel: 369 | dependency: transitive 370 | description: 371 | name: stream_channel 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "2.1.1" 375 | stream_transform: 376 | dependency: transitive 377 | description: 378 | name: stream_transform 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "2.0.1" 382 | string_scanner: 383 | dependency: transitive 384 | description: 385 | name: string_scanner 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "1.1.1" 389 | term_glyph: 390 | dependency: transitive 391 | description: 392 | name: term_glyph 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "1.2.1" 396 | test: 397 | dependency: "direct dev" 398 | description: 399 | name: test 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "1.22.2" 403 | test_api: 404 | dependency: transitive 405 | description: 406 | name: test_api 407 | url: "https://pub.dartlang.org" 408 | source: hosted 409 | version: "0.4.18" 410 | test_core: 411 | dependency: transitive 412 | description: 413 | name: test_core 414 | url: "https://pub.dartlang.org" 415 | source: hosted 416 | version: "0.4.22" 417 | typed_data: 418 | dependency: transitive 419 | description: 420 | name: typed_data 421 | url: "https://pub.dartlang.org" 422 | source: hosted 423 | version: "1.3.1" 424 | vm_service: 425 | dependency: transitive 426 | description: 427 | name: vm_service 428 | url: "https://pub.dartlang.org" 429 | source: hosted 430 | version: "9.4.0" 431 | watcher: 432 | dependency: transitive 433 | description: 434 | name: watcher 435 | url: "https://pub.dartlang.org" 436 | source: hosted 437 | version: "1.0.1" 438 | web_socket_channel: 439 | dependency: transitive 440 | description: 441 | name: web_socket_channel 442 | url: "https://pub.dartlang.org" 443 | source: hosted 444 | version: "2.2.0" 445 | webkit_inspection_protocol: 446 | dependency: transitive 447 | description: 448 | name: webkit_inspection_protocol 449 | url: "https://pub.dartlang.org" 450 | source: hosted 451 | version: "1.2.0" 452 | yaml: 453 | dependency: transitive 454 | description: 455 | name: yaml 456 | url: "https://pub.dartlang.org" 457 | source: hosted 458 | version: "3.1.1" 459 | sdks: 460 | dart: ">=2.18.0 <3.0.0" 461 | -------------------------------------------------------------------------------- /generator/lib/code_builders/method_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:analyzer/dart/element/element.dart'; 4 | import 'package:code_builder/code_builder.dart'; 5 | import 'package:smartstruct_generator/code_builders/parameter_copy.dart'; 6 | import 'package:smartstruct_generator/models/RefChain.dart'; 7 | import 'package:smartstruct_generator/models/source_assignment.dart'; 8 | import 'package:smartstruct_generator/mapper_config.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | 11 | import 'assignment_builder.dart'; 12 | 13 | /// Generates the implemented mapper method by the given abstract [MethodElement]. 14 | Method buildMapperImplementation(Map config, 15 | MethodElement method, ClassElement abstractMapper) { 16 | if (method.returnType.element2 == null) { 17 | throw InvalidGenerationSourceError( 18 | '${method.returnType} is not a valid return type', 19 | element: method, 20 | todo: 'Add valid return type to mapping method'); 21 | } 22 | return Method((b) => b 23 | ..annotations.add(CodeExpression(Code('override'))) 24 | ..name = method.displayName 25 | ..requiredParameters.addAll(method.parameters.map((e) => copyParameter(e))) 26 | ..body = _generateBody(config, method, abstractMapper) 27 | ..returns = 28 | refer(method.returnType.getDisplayString(withNullability: true))); 29 | } 30 | 31 | 32 | /// Generates the implemented mapper method by the given abstract [MethodElement]. 33 | Method buildStaticMapperImplementation(Map config, 34 | MethodElement method, ClassElement abstractMapper) { 35 | return Method( 36 | (b) => b 37 | ..name = '_\$${method.name}' 38 | ..requiredParameters.addAll(method.parameters.map((e) => copyParameter(e))) 39 | ..body = _generateBody(config, method, abstractMapper) 40 | ..returns = 41 | refer(method.returnType.getDisplayString(withNullability: true)), 42 | ); 43 | } 44 | 45 | 46 | /// Generates the body for the mapping method. 47 | /// 48 | /// Uses the default constructor of the target mapping class to populate optional and required named and positional parameters. 49 | /// If the target class has any properties which were not set in the constructor, and are mappable by the source, they will be also mapped after initializing the target instance. 50 | Code _generateBody(Map config, MethodElement method, 51 | ClassElement abstractMapper) { 52 | final blockBuilder = BlockBuilder(); 53 | 54 | final targetClass = method.returnType.element2 as ClassElement; 55 | 56 | final sourceParams = method.parameters; 57 | 58 | final _ = _targetToSource(sourceParams, targetClass, method, config); 59 | final targetToSource = _[0]; 60 | // final customTargetToSource = _[1]; 61 | 62 | final targetConstructor = _chooseConstructor(targetClass); 63 | final positionalArgs = []; 64 | final namedArgs = {}; 65 | 66 | // fills namedArgs and positionalArgs for the targetConstructor if 67 | // one of the inputfields matches the current constructorfield 68 | targetConstructor.parameters 69 | .where((targetField) => targetToSource.containsKey(targetField.name)) 70 | .forEach((targetField) { 71 | final sourceAssignment = targetToSource[targetField.name]!; 72 | var sourceFieldAssignment = generateSourceFieldAssignment( 73 | sourceAssignment, abstractMapper, targetField); 74 | 75 | if (targetField.isNamed) { 76 | namedArgs.putIfAbsent(targetField.name, () => sourceFieldAssignment); 77 | } else { 78 | positionalArgs.add(sourceFieldAssignment); 79 | } 80 | targetToSource.remove(targetField.name); 81 | }); 82 | 83 | var targetVarName = targetClass.displayName.toLowerCase(); 84 | 85 | // source.isOptional does not work 86 | for (final sourceParam in sourceParams) { 87 | if (sourceParam.type 88 | .getDisplayString(withNullability: true) 89 | .endsWith('?')) { 90 | blockBuilder.addExpression( 91 | refer('if (${sourceParam.displayName} == null) { return null; }')); 92 | } 93 | } 94 | // final output = Output(positionalArgs, {namedArgs}); 95 | blockBuilder.addExpression(refer(targetConstructor.displayName) 96 | .newInstance(positionalArgs, namedArgs) 97 | .assignFinal(targetVarName)); 98 | 99 | // non final properties (implicit and explicit setters) 100 | final fields = _findFields(targetClass); 101 | fields // 102 | .where((field) => !field.isFinal) // 103 | .where( 104 | (targetField) => targetToSource.containsKey(targetField.displayName)) 105 | .map((targetField) { 106 | var sourceAssignment = targetToSource[targetField.displayName]!; 107 | var sourceFieldAssignment = generateSourceFieldAssignment( 108 | sourceAssignment, abstractMapper, targetField); 109 | return refer(targetVarName) 110 | .property(targetField.displayName) 111 | .assign(sourceFieldAssignment); 112 | }).forEach((expr) => blockBuilder.addExpression(expr)); 113 | 114 | blockBuilder.addExpression(refer(targetVarName).returned); 115 | return blockBuilder.build(); 116 | } 117 | 118 | /// Chooses the constructor which will be used to instantiate the target class. 119 | ConstructorElement _chooseConstructor(ClassElement outputClass) { 120 | ConstructorElement chosen = 121 | outputClass.constructors.where((element) => !element.isFactory).first; 122 | for (var con in outputClass.constructors) { 123 | if (con.parameters.length >= chosen.parameters.length) { 124 | // choose the one with the most parameters 125 | chosen = con; 126 | } 127 | } 128 | return chosen; 129 | } 130 | 131 | List _findFields(ClassElement clazz) { 132 | final allSuperclasses = clazz.allSupertypes 133 | .where((element) => !element.isDartCoreObject) 134 | .map((e) => e.element) 135 | .toList(); 136 | 137 | final allAccessors = allSuperclasses.map((e) => e.accessors).expand((e) => e); 138 | final accessorMap = {for (var e in allAccessors) e.displayName: e}; 139 | 140 | // ignore: prefer_function_declarations_over_variables 141 | final fieldFilter = (FieldElement field) { 142 | var isAbstract = false; 143 | // fields, who can also be getters, are never abstract, only their PropertyAccessorElement (implicit getter) 144 | if (accessorMap.containsKey(field.displayName)) { 145 | final accessor = accessorMap[field.displayName]!; 146 | isAbstract = accessor.isAbstract; 147 | } 148 | return !field.isStatic && !field.isConst && !isAbstract; 149 | }; 150 | 151 | final allSuperFields = allSuperclasses 152 | .map((e) => e.fields) 153 | .expand((e) => e) 154 | .where(fieldFilter) 155 | .toList(); 156 | return [...clazz.fields, ...allSuperFields]; 157 | } 158 | 159 | List> _targetToSource( 160 | List sources, 161 | ClassElement target, 162 | MethodElement method, 163 | Map config) { 164 | final sourceMap = {for (var e in sources) e.type.element2 as ClassElement: e}; 165 | 166 | final caseSensitiveFields = config['caseSensitiveFields']; 167 | final fieldMapper = caseSensitiveFields ? (a) => a : (a) => a.toUpperCase(); 168 | final equalsHashCode = 169 | caseSensitiveFields ? (a) => a.hashCode : (a) => a.toUpperCase().hashCode; 170 | final mappingConfig = MapperConfig.readMappingConfig(method); 171 | // final customMappingConfig = MapperConfig.readCustomMappingConfig(method); 172 | 173 | /// With HashMap you can specify how to compare keys 174 | /// It is very usefull when you want to have caseInsensitive keys 175 | /// Contains data from @Mapping annotations 176 | var targetToSource = HashMap( 177 | equals: (a, b) => fieldMapper(a) == fieldMapper(b), 178 | hashCode: (a) => equalsHashCode(a)); 179 | 180 | /// Contains data from @CustomMapping annotations 181 | var customTargetToSource = HashMap( 182 | equals: (a, b) => fieldMapper(a) == fieldMapper(b), 183 | hashCode: (a) => equalsHashCode(a)); 184 | 185 | final mappingStringConfig = _extractStringMappingConfig(mappingConfig); 186 | 187 | for (final sourceEntry in sourceMap.entries) { 188 | Map> matchedSourceClazzInSourceMapping = 189 | _findMatchingSourceClazzInMappingMap( 190 | mappingStringConfig, sourceEntry.value.displayName); 191 | for (var f in _findFields(sourceEntry.key)) { 192 | if (targetToSource.containsKey(f.name) && !caseSensitiveFields) { 193 | final duplicatedKey = targetToSource.keys 194 | .toList() 195 | .firstWhere((k) => k.toUpperCase() == f.name.toUpperCase()); 196 | throw InvalidGenerationSourceError( 197 | 'Mapper got case insensitive fields and contains fields: ${f.name} and $duplicatedKey. If you use a case-sensitive mapper, make sure the fields are unique in a case insensitive way.', 198 | todo: "Use case sensitive mapper or change field's names"); 199 | } 200 | if (matchedSourceClazzInSourceMapping.isNotEmpty && 201 | _shouldSearchMoreFields(f)) { 202 | for (var matchedTarget in matchedSourceClazzInSourceMapping.keys) { 203 | final sourceValueList = 204 | matchedSourceClazzInSourceMapping[matchedTarget]!; 205 | final fieldClazz = f.type.element2 as ClassElement; 206 | 207 | final refChain = RefChain.byPropNames(sourceEntry.value, sourceValueList.sublist(1)); 208 | targetToSource[matchedTarget] = SourceAssignment.fromRefChain(refChain); 209 | } 210 | } else { 211 | targetToSource[f.name] = 212 | SourceAssignment.fromRefChain(RefChain([sourceEntry.value, f])); 213 | } 214 | } 215 | } 216 | 217 | /// If there are Mapping Annotations on the method, the source attribute of the source mapping class, 218 | /// will be replaced with the source attribute of the given mapping config. 219 | mappingConfig.forEach((targetField, mappingConfig) { 220 | final sourceField = mappingConfig.source; 221 | if (sourceField != null) { 222 | if (sourceField.toFunctionValue() != null) { 223 | targetToSource[targetField] = SourceAssignment.fromFunction( 224 | sourceField.toFunctionValue()!, [...sources]); 225 | } else if (sourceField.toStringValue() != null) { 226 | final sourceFieldString = sourceField.toStringValue()!; 227 | // sourceField exists in any sourceParam 228 | if (targetToSource.containsKey(sourceFieldString)) { 229 | // replace mapping target with mapping 230 | targetToSource.putIfAbsent( 231 | targetField, () => targetToSource[sourceFieldString]!); 232 | targetToSource.remove(sourceFieldString); 233 | } 234 | } 235 | } 236 | 237 | if (mappingConfig.ignore) { 238 | targetToSource.remove(targetField); 239 | } 240 | }); 241 | 242 | return [targetToSource, customTargetToSource]; 243 | } 244 | 245 | /// Extracts all Mapping Config Entries in [mappingConfig] which contains source mappings of type string 246 | Map _extractStringMappingConfig( 247 | Map mappingConfig) { 248 | final mappingStringConfig = {}; 249 | mappingConfig.forEach((key, value) { 250 | if (value.source != null && value.source!.toStringValue() != null) { 251 | mappingStringConfig.putIfAbsent(key, () => value); 252 | } 253 | }); 254 | return mappingStringConfig; 255 | } 256 | 257 | /// Searches for a matching class for every given [MappingConfig] in [mappingStringConfig], matched against the given [matchingSourceClazzName] 258 | /// For MappingConfigs including dot seperated clazz attributes, the first value before the first dot is matched against the given matchingSourceClazzName. 259 | /// Example: A MappingConfig containing "user.address.zipcode" would try to match against user 260 | List> _findMatchingSourceClazzInMapping( 261 | Map mappingStringConfig, 262 | String matchingSourceClazzName) { 263 | List> matchedSourceClazzInSourceMapping = []; 264 | mappingStringConfig.forEach((key, value) { 265 | // clazz.attribute1.attribute1_1 266 | final sourceValueList = value.source!.toStringValue()!.split("."); 267 | final sourceClass = sourceValueList[0]; 268 | if (sourceClass == matchingSourceClazzName) { 269 | matchedSourceClazzInSourceMapping.add(sourceValueList); 270 | } 271 | }); 272 | return matchedSourceClazzInSourceMapping; 273 | } 274 | 275 | Map> _findMatchingSourceClazzInMappingMap( 276 | Map mappingStringConfig, 277 | String matchingSourceClazzName) { 278 | Map> ret = {}; 279 | mappingStringConfig.forEach((key, value) { 280 | // clazz.attribute1.attribute1_1 281 | final sourceValueList = value.source!.toStringValue()!.split("."); 282 | final sourceClass = sourceValueList[0]; 283 | if (sourceClass == matchingSourceClazzName) { 284 | ret.putIfAbsent(key, () => sourceValueList); 285 | } 286 | }); 287 | return ret; 288 | } 289 | 290 | /// Finds the matching field, matching the last source of [sources] to any field of [fields] 291 | /// If no field was found, null is returned 292 | /// 293 | /// Example: [sources]="user,address,zipcode" with [fields]=address would identify address as a field, then continue searching in the address field for the zipcode field. 294 | /// If the address contains a field zipcode, the zipcode field is returned. 295 | FieldElement? _findMatchingField( 296 | List sources, List fields) { 297 | for (var source in sources) { 298 | final potentielFinds = fields.where((element) => element.name == source); 299 | if (potentielFinds.isEmpty) { 300 | continue; 301 | } 302 | final foundField = potentielFinds.first; 303 | // foundField is not string 304 | if (_shouldSearchMoreFields(foundField)) { 305 | final searchClazz = foundField.type.element2 as ClassElement; 306 | return _findMatchingField( 307 | sources.skip(1).toList(), _findFields(searchClazz)); 308 | } else { 309 | return foundField; 310 | } 311 | } 312 | } 313 | 314 | /// A search for a potential underlying should only be continued, if the field is not a primitive type (string, int, double etc) 315 | bool _shouldSearchMoreFields(FieldElement field) { 316 | return !field.type.isDartCoreString && 317 | !field.type.isDartCoreBool && 318 | !field.type.isDartCoreDouble && 319 | !field.type.isDartCoreFunction && 320 | !field.type.isDartCoreInt && 321 | !field.type.isDartCoreIterable && 322 | !field.type.isDartCoreList && 323 | !field.type.isDartCoreMap && 324 | !field.type.isDartCoreNull && 325 | !field.type.isDartCoreNum && 326 | !field.type.isDartCoreSet; 327 | } 328 | -------------------------------------------------------------------------------- /smartstruct/README.md: -------------------------------------------------------------------------------- 1 | # Smartstruct - Dart bean mappings - the easy nullsafe way! 2 | 3 | Code generator for generating type-safe mappers in dart, inspired by https://mapstruct.org/ 4 | 5 | - [Installation](#installation) 6 | - [Usage](#usage) 7 | - [Examples](#examples) 8 | - [Roadmap](#roadmap) 9 | 10 | # Overview 11 | 12 | - Add smartstruct as a dependency, and smartstruct_generator as a dev_dependency 13 | - Create a Mapper class 14 | - Annotate the class with @mapper 15 | - Run the build_runner 16 | - Use the generated Mapper! 17 | 18 | # Installation 19 | 20 | Add smartstruct as a dependency, and the generator as a dev_dependency. 21 | 22 | https://pub.dev/packages/smartstruct 23 | 24 | ```yaml 25 | dependencies: 26 | smartstruct: [version] 27 | 28 | dev_dependencies: 29 | smartstruct_generator: [version] 30 | # add build runner if not already added 31 | build_runner: 32 | ``` 33 | 34 | Run the generator 35 | 36 | ```console 37 | dart run build_runner build 38 | flutter packages pub run build_runner build 39 | // or watch 40 | flutter packages pub run build_runner watch 41 | ``` 42 | 43 | # Usage 44 | 45 | Create your beans. 46 | 47 | ```dart 48 | class Dog { 49 | final String breed; 50 | final int age; 51 | final String name; 52 | Dog(this.breed, this.age, this.name); 53 | } 54 | ``` 55 | 56 | ```dart 57 | class DogModel { 58 | final String breed; 59 | final int age; 60 | final String name; 61 | DogModel(this.breed, this.age, this.name); 62 | } 63 | ``` 64 | 65 | To generate a mapper for these two beans, you need to create a mapper interface. 66 | 67 | ```dart 68 | // dogmapper.dart 69 | part 'dogmapper.mapper.g.dart'; 70 | 71 | @Mapper() 72 | abstract class DogMapper { 73 | Dog fromModel(DogModel model); 74 | } 75 | ``` 76 | 77 | Once you ran the generator, next to your _dog.mapper.dart_ a _dog.mapper.g.dart_ will be generated. 78 | 79 | ``` 80 | dart run build_runner build 81 | ``` 82 | 83 | ```dart 84 | // dogmapper.mapper.g.dart 85 | class DogMapperImpl extends DogMapper { 86 | @override 87 | Dog fromModel(DogModel model) { 88 | Dog dog = Dog(model.breed, model.age, model.name); 89 | return dog; 90 | } 91 | } 92 | ``` 93 | 94 | The Mapper supports positional arguments, named arguments and property access via implicit and explicit setters. 95 | 96 | ## Case sensitivity 97 | 98 | By default mapper generator works in case insensitivity manner. 99 | 100 | ```dart 101 | class Source { 102 | final String userName; 103 | 104 | Source(this.userName); 105 | } 106 | 107 | class Target { 108 | final String username; 109 | 110 | Target({required this.username}); 111 | } 112 | 113 | @Mapper() 114 | abstract class ExampleMapper { 115 | Target fromSource(Source source); 116 | } 117 | ``` 118 | 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. 119 | ```dart 120 | 121 | class ExampleMapperImpl extends ExampleMapper { 122 | @override 123 | Target fromSource(Source source) { 124 | final target = Target(username: source.userName); 125 | return target; 126 | } 127 | } 128 | ``` 129 | 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. 130 | ```dart 131 | 132 | @Mapper(caseSensitiveFields: true) 133 | abstract class ExampleMapper { 134 | Target fromSource(Source source); 135 | } 136 | ``` 137 | 138 | 139 | ## Explicit Field Mapping 140 | 141 | 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. 142 | 143 | ```dart 144 | class Dog { 145 | final String name; 146 | Dog(this.name); 147 | } 148 | class DogModel { 149 | final String dogName; 150 | DogModel(this.dogName); 151 | } 152 | ``` 153 | 154 | ```dart 155 | @Mapper() 156 | class DogMapper { 157 | @Mapping(source: 'dogName', target: 'name') 158 | Dog fromModel(DogModel model); 159 | } 160 | ``` 161 | 162 | In this case, the field _dogName_ of _DogModel_ will be mapped to the field _name_ of the resulting _Dog_ 163 | 164 | ```dart 165 | class DogMapperImpl extends DogMapper { 166 | @override 167 | Dog fromModel(DogModel model) { 168 | Dog dog = Dog(model.dogName); 169 | return dog; 170 | } 171 | } 172 | ``` 173 | 174 | ### Function Mapping 175 | 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. 176 | ```dart 177 | class Dog { 178 | final String name; 179 | final String breed; 180 | Dog(this.name, this.breed); 181 | } 182 | class DogModel { 183 | final String name; 184 | DogModel(this.name); 185 | } 186 | ``` 187 | 188 | ```dart 189 | @Mapper() 190 | class DogMapper { 191 | @IgnoreMapping() 192 | static String randomBreed(DogModel model) => 'some random breed'; 193 | 194 | @Mapping(source: randomBreed, target: 'breed') 195 | Dog fromModel(DogModel model); 196 | } 197 | ``` 198 | 199 | Note the `@IgnoreMapping` Annotation, to make sure that no static implementation for this mapper method is created. 200 | See **Static Mapping** 201 | 202 | Will generate the following Mapper. 203 | ```dart 204 | class DogMapperImpl extends DogMapper { 205 | @override 206 | Dog fromModel(DogModel model) { 207 | Dog dog = Dog(model.dogName, DogMapper.randomBreed(model)); 208 | return dog; 209 | } 210 | } 211 | ``` 212 | ### Ignore Fields 213 | Fields can be ignored, by specififying the `ignore` attribute on the Mapping `Annotation`` 214 | 215 | ```dart 216 | class Dog { 217 | final String name; 218 | String? breed; 219 | Dog(this.name); 220 | } 221 | class DogModel { 222 | final String name; 223 | final String breed; 224 | DogModel(this.name, this.breed); 225 | } 226 | ``` 227 | 228 | ```dart 229 | @Mapper() 230 | class DogMapper { 231 | @Mapping(target: 'breed', ignore: true) 232 | Dog fromModel(DogModel model); 233 | } 234 | ``` 235 | 236 | Will generate the following Mapper. 237 | ```dart 238 | class DogMapperImpl extends DogMapper { 239 | @override 240 | Dog fromModel(DogModel model) { 241 | Dog dog = Dog(model.name); 242 | return dog; 243 | } 244 | } 245 | ``` 246 | 247 | ## Nested Bean Mapping 248 | 249 | Nested beans can be mapped, by defining an additional mapper method for the nested bean. 250 | 251 | ```dart 252 | // nestedmapper.dart 253 | class NestedTarget { 254 | final SubNestedTarget subNested; 255 | NestedTarget(this.subNested); 256 | } 257 | class SubNestedTarget { 258 | final String myProperty; 259 | SubNestedTarget(this.myProperty); 260 | } 261 | 262 | class NestedSource { 263 | final SubNestedSource subNested; 264 | NestedSource(this.subNested); 265 | } 266 | 267 | class SubNestedSource { 268 | final String myProperty; 269 | SubNestedSource(this.myProperty); 270 | } 271 | 272 | @Mapper() 273 | abstract class NestedMapper { 274 | NestedTarget fromModel(NestedSource model); 275 | 276 | SubNestedTarget fromSubClassModel(SubNestedSource model); 277 | } 278 | ``` 279 | 280 | Will generate the mapper 281 | 282 | ```dart 283 | // nestedmapper.mapper.g.dart 284 | class NestedMapperImpl extends NestedMapper { 285 | @override 286 | NestedTarget fromModel(NestedSource model) { 287 | final nestedtarget = NestedTarget(fromSubClassModel(model.subNested)); 288 | return nestedtarget; 289 | } 290 | 291 | @override 292 | SubNestedTarget fromSubClassModel(SubNestedSource model) { 293 | final subnestedtarget = SubNestedTarget(model.myProperty); 294 | return subnestedtarget; 295 | } 296 | } 297 | 298 | ``` 299 | 300 | Alternatively you can directly define the nested mapping in the source attribute. 301 | 302 | ```dart 303 | class User { 304 | final String username; 305 | final String zipcode; 306 | final String street; 307 | 308 | User(this.username, this.zipcode, this.street); 309 | } 310 | 311 | class UserResponse { 312 | final String username; 313 | final AddressResponse address; 314 | 315 | UserResponse(this.username, this.address); 316 | } 317 | 318 | class AddressResponse { 319 | final String zipcode; 320 | final StreetResponse street; 321 | 322 | AddressResponse(this.zipcode, this.street); 323 | } 324 | 325 | class StreetResponse { 326 | final num streetNumber; 327 | final String streetName; 328 | 329 | StreetResponse(this.streetNumber, this.streetName); 330 | } 331 | ``` 332 | With this, you can define the mappings directly in the `Mapping` Annotation 333 | 334 | ```dart 335 | @Mapper() 336 | abstract class UserMapper { 337 | @Mapping(target: 'zipcode', source: 'response.address.zipcode') 338 | @Mapping(target: 'street', source: 'response.address.street.streetName') 339 | User fromResponse(UserResponse response); 340 | } 341 | ``` 342 | 343 | Would generate the following mapper. 344 | ```dart 345 | class UserMapperImpl extends UserMapper { 346 | UserMapperImpl() : super(); 347 | 348 | @override 349 | User fromResponse(UserResponse response) { 350 | final user = User(response.username, response.address.zipcode, 351 | response.address.street.streetName); 352 | return user; 353 | } 354 | } 355 | ``` 356 | 357 | ## List Support 358 | Lists will be mapped as new instances of a list, with help of the map method. 359 | ```dart 360 | class Source { 361 | final List intList; 362 | final List entryList; 363 | 364 | Source(this.intList, this.entryList); 365 | } 366 | 367 | class SourceEntry { 368 | final String prop; 369 | 370 | SourceEntry(this.prop); 371 | } 372 | 373 | class Target { 374 | final List intList; 375 | final List entryList; 376 | 377 | Target(this.intList, this.entryList); 378 | } 379 | 380 | class TargetEntry { 381 | final String prop; 382 | 383 | TargetEntry(this.prop); 384 | } 385 | 386 | @Mapper() 387 | abstract class ListMapper { 388 | Target fromSource(Source source); 389 | TargetEntry fromSourceEntry(SourceEntry source); 390 | } 391 | ``` 392 | Will generate the Mapper 393 | 394 | ```dart 395 | class ListMapperImpl extends ListMapper { 396 | @override 397 | Target fromSource(Source source) { 398 | final target = Target( 399 | source.intList.map((e) => e).toList(), 400 | source.entryList.map(fromSourceEntry).toList()); 401 | return target; 402 | } 403 | 404 | @override 405 | TargetEntry fromSourceEntry(SourceEntry source) { 406 | final targetentry = TargetEntry(source.prop); 407 | return targetentry; 408 | } 409 | } 410 | ``` 411 | 412 | ## Injectable 413 | 414 | The Mapper can be made a lazy injectable singleton, by setting the argument _useInjection_ to true, in the Mapper Interface. 415 | In this case you also need to add the injectable dependency, as described here. https://pub.dev/packages/injectable 416 | 417 | Make sure, that in the Mapper File, you import the injectable dependency, before running the build_runner! 418 | 419 | ```dart 420 | // dogmapper.dart 421 | 422 | import 'package:injectable/injectable.dart'; 423 | 424 | @Mapper(useInjectable = true) 425 | abstract class DogMapper { 426 | Dog fromModel(DogModel model); 427 | } 428 | ``` 429 | 430 | ```dart 431 | // dogmapper.mapper.g.dart 432 | @LazySingleton(as: DogMapper) 433 | class DogMapperImpl extends DogMapper {...} 434 | ``` 435 | 436 | ## Freezed 437 | Generally you can use smartstruct with [freezed](https://pub.dev/packages/freezed). 438 | 439 | One problem you will have to manually workaround is ignoring the freezed generated `copyWith` method in the generated mapper. 440 | The copyWith field is a normal field in the model / entity, and smartstruct does not have a way of knowing on when to filter it out, and when not. 441 | 442 | Imagine having the following freezed models. 443 | ```dart 444 | @freezed 445 | class Dog with _$Dog { 446 | Dog._(); 447 | factory Dog(String name) = _Dog; 448 | } 449 | 450 | @freezed 451 | class DogModel with _$DogModel { 452 | factory DogModel(String name) = _DogModel; 453 | } 454 | ``` 455 | 456 | Freezed will generate a `copyWith` field for your `Dog` and `DogModel`. 457 | 458 | When generating the mapper, you explicitly have to ignore this field. 459 | ```dart 460 | @Mapper() 461 | abstract class DogMapper { 462 | @Mapping(target: 'copyWith', ignore: true) 463 | Dog fromModel(DogModel model); 464 | } 465 | ``` 466 | Will generate the mapper, using the factory constructor. 467 | ```dart 468 | class DogMapperImpl extends DogMapper { 469 | DogMapperImpl() : super(); 470 | 471 | @override 472 | Dog fromModel(DogModel model) { 473 | final dog = Dog(model.name); 474 | return freezedtarget; 475 | } 476 | } 477 | ``` 478 | 479 | ## Static Mapping 480 | Static Methods in a Mapper Class will automatically be mapped with a static pendant in the generated mapper file. 481 | 482 | 483 | ```dart 484 | class Dog { 485 | final String name; 486 | Dog(this.name); 487 | } 488 | class DogModel { 489 | final String name; 490 | DogModel(this.name); 491 | } 492 | ``` 493 | 494 | ```dart 495 | @Mapper() 496 | class DogMapper { 497 | static Dog fromModel(DogModel model) => _$fromModel(model); 498 | } 499 | ``` 500 | 501 | Will generate a mapper file providing the following static methods. 502 | ```dart 503 | Dog _$fromModel(DogModel model) { 504 | final dog = Dog(model.name); 505 | return dog; 506 | } 507 | ``` 508 | 509 | ## Static Mapping with a proxy 510 | Alternatively you can set ``generateStaticProxy`` to ``true``in the Mapping Annotation, to generate a Mapper Proxy implementation for your static methods. 511 | ```dart 512 | class Dog { 513 | final String name; 514 | Dog(this.name); 515 | } 516 | class DogModel { 517 | final String name; 518 | DogModel(this.name); 519 | } 520 | ``` 521 | 522 | ```dart 523 | @Mapper(generateStaticProxy = true) 524 | class DogMapper { 525 | Dog fromModel(DogModel model); 526 | } 527 | ``` 528 | 529 | Will generate the following mapper. 530 | ```dart 531 | class DogMapperImpl extends DogMapper { 532 | DogMapperImpl() : super(); 533 | 534 | @override 535 | Dog fromModel(DogModel model) { 536 | final dog = Dog(model.name); 537 | return dog; 538 | } 539 | } 540 | 541 | class DogMapper$ { 542 | static final DogMapper mapper = DogMapperImpl(); 543 | 544 | static Dog fromModel(DogModel model) => 545 | mapper.fromModel(model); 546 | } 547 | ``` 548 | 549 | # Examples 550 | 551 | Please refer to the [example](https://github.com/smotastic/smartstruct/tree/master/example) package, for a list of examples and how to use the Mapper Annotation. 552 | 553 | You can always run the examples by navigating to the examples package and executing the generator. 554 | 555 | ```console 556 | $ dart pub get 557 | ... 558 | $ dart run build_runner build 559 | ``` 560 | 561 | # Roadmap 562 | 563 | Feel free to open a [Pull Request](https://github.com/smotastic/smartstruct/pulls), if you'd like to contribute. 564 | 565 | Or just open an issue, and i do my level best to deliver. 566 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smartstruct - Dart bean mappings - the easy nullsafe way! 2 | 3 | Code generator for generating type-safe mappers in dart, inspired by https://mapstruct.org/ 4 | 5 | - [Installation](#installation) 6 | - [Usage](#usage) 7 | - [Examples](#examples) 8 | - [Roadmap](#roadmap) 9 | 10 | # Overview 11 | 12 | - Add smartstruct as a dependency, and smartstruct_generator as a dev_dependency 13 | - Create a Mapper class 14 | - Annotate the class with @mapper 15 | - Run the build_runner 16 | - Use the generated Mapper! 17 | 18 | # Installation 19 | 20 | Add smartstruct as a dependency, and the generator as a dev_dependency. 21 | 22 | https://pub.dev/packages/smartstruct 23 | 24 | ```yaml 25 | dependencies: 26 | smartstruct: [ version ] 27 | 28 | dev_dependencies: 29 | smartstruct_generator: [ version ] 30 | # add build runner if not already added 31 | build_runner: 32 | ``` 33 | 34 | Run the generator 35 | 36 | ```console 37 | dart run build_runner build 38 | flutter packages pub run build_runner build 39 | // or watch 40 | flutter packages pub run build_runner watch 41 | ``` 42 | 43 | # Usage 44 | 45 | Create your beans. 46 | 47 | ```dart 48 | class Dog { 49 | final String breed; 50 | final int age; 51 | final String name; 52 | 53 | Dog(this.breed, this.age, this.name); 54 | } 55 | ``` 56 | 57 | ```dart 58 | class DogModel { 59 | final String breed; 60 | final int age; 61 | final String name; 62 | 63 | DogModel(this.breed, this.age, this.name); 64 | } 65 | ``` 66 | 67 | To generate a mapper for these two beans, you need to create a mapper interface. 68 | 69 | ```dart 70 | // dogmapper.dart 71 | part 'dogmapper.mapper.g.dart'; 72 | 73 | @Mapper() 74 | abstract class DogMapper { 75 | Dog fromModel(DogModel model); 76 | } 77 | ``` 78 | 79 | Once you ran the generator, next to your _dog.mapper.dart_ a _dog.mapper.g.dart_ will be generated. 80 | 81 | ``` 82 | dart run build_runner build 83 | ``` 84 | 85 | ```dart 86 | // dogmapper.mapper.g.dart 87 | class DogMapperImpl extends DogMapper { 88 | @override 89 | Dog fromModel(DogModel model) { 90 | Dog dog = Dog(model.breed, model.age, model.name); 91 | return dog; 92 | } 93 | } 94 | ``` 95 | 96 | The Mapper supports positional arguments, named arguments and property access via implicit and explicit setters. 97 | 98 | ## Case sensitivity 99 | 100 | By default mapper generator works in case insensitivity manner. 101 | 102 | ```dart 103 | class Source { 104 | final String userName; 105 | 106 | Source(this.userName); 107 | } 108 | 109 | class Target { 110 | final String username; 111 | 112 | Target({required this.username}); 113 | } 114 | 115 | @Mapper() 116 | abstract class ExampleMapper { 117 | Target fromSource(Source source); 118 | } 119 | ``` 120 | 121 | As you can see, classes above got different field's names (case) for username. Because mappers are case insensitive by 122 | default, those classes are correctly mapped. 123 | 124 | ```dart 125 | 126 | class ExampleMapperImpl extends ExampleMapper { 127 | @override 128 | Target fromSource(Source source) { 129 | final target = Target(username: source.userName); 130 | return target; 131 | } 132 | } 133 | ``` 134 | 135 | To create case sensitive mapper, you can add param caseSensitiveFields to @Mapper annotation. Case sensitive mapper is 136 | checking field's names in case sensitive manner. 137 | 138 | ```dart 139 | 140 | @Mapper(caseSensitiveFields: true) 141 | abstract class ExampleMapper { 142 | Target fromSource(Source source); 143 | } 144 | ``` 145 | 146 | ## Explicit Field Mapping 147 | 148 | If some fields do not match each other, you can add a Mapping Annotation on the method level, to change the behaviour of 149 | certain mappings. 150 | 151 | ```dart 152 | class Dog { 153 | final String name; 154 | 155 | Dog(this.name); 156 | } 157 | 158 | class DogModel { 159 | final String dogName; 160 | 161 | DogModel(this.dogName); 162 | } 163 | ``` 164 | 165 | ```dart 166 | @Mapper() 167 | class DogMapper { 168 | @Mapping(source: 'dogName', target: 'name') 169 | Dog fromModel(DogModel model); 170 | } 171 | ``` 172 | 173 | In this case, the field _dogName_ of _DogModel_ will be mapped to the field _name_ of the resulting _Dog_ 174 | 175 | ```dart 176 | class DogMapperImpl extends DogMapper { 177 | @override 178 | Dog fromModel(DogModel model) { 179 | Dog dog = Dog(model.dogName); 180 | return dog; 181 | } 182 | } 183 | ``` 184 | 185 | ### Function Mapping 186 | 187 | The source attribute can also be a Function. This Function will then be called with the Source Parameter of the mapper 188 | method as a parameter. 189 | 190 | ```dart 191 | class Dog { 192 | final String name; 193 | final String breed; 194 | 195 | Dog(this.name, this.breed); 196 | } 197 | 198 | class DogModel { 199 | final String name; 200 | 201 | DogModel(this.name); 202 | } 203 | ``` 204 | 205 | ```dart 206 | @Mapper() 207 | class DogMapper { 208 | static String randomBreed(DogModel model) => 'some random breed'; 209 | 210 | @Mapping(source: randomBreed, target: 'breed') 211 | Dog fromModel(DogModel model); 212 | } 213 | ``` 214 | 215 | Will generate the following Mapper. 216 | 217 | ```dart 218 | class DogMapperImpl extends DogMapper { 219 | @override 220 | Dog fromModel(DogModel model) { 221 | Dog dog = Dog(model.dogName, DogMapper.randomBreed(model)); 222 | return dog; 223 | } 224 | } 225 | ``` 226 | 227 | ### Ignore Fields 228 | 229 | Fields can be ignored, by specififying the `ignore` attribute on the Mapping `Annotation`` 230 | 231 | ```dart 232 | class Dog { 233 | final String name; 234 | String? breed; 235 | 236 | Dog(this.name); 237 | } 238 | 239 | class DogModel { 240 | final String name; 241 | final String breed; 242 | 243 | DogModel(this.name, this.breed); 244 | } 245 | ``` 246 | 247 | ```dart 248 | @Mapper() 249 | class DogMapper { 250 | @Mapping(target: 'breed', ignore: true) 251 | Dog fromModel(DogModel model); 252 | } 253 | ``` 254 | 255 | Will generate the following Mapper. 256 | 257 | ```dart 258 | class DogMapperImpl extends DogMapper { 259 | @override 260 | Dog fromModel(DogModel model) { 261 | Dog dog = Dog(model.name); 262 | return dog; 263 | } 264 | } 265 | ``` 266 | 267 | ## Nested Bean Mapping 268 | 269 | Nested beans can be mapped, by defining an additional mapper method for the nested bean. 270 | 271 | ```dart 272 | // nestedmapper.dart 273 | class NestedTarget { 274 | final SubNestedTarget subNested; 275 | 276 | NestedTarget(this.subNested); 277 | } 278 | 279 | class SubNestedTarget { 280 | final String myProperty; 281 | 282 | SubNestedTarget(this.myProperty); 283 | } 284 | 285 | class NestedSource { 286 | final SubNestedSource subNested; 287 | 288 | NestedSource(this.subNested); 289 | } 290 | 291 | class SubNestedSource { 292 | final String myProperty; 293 | 294 | SubNestedSource(this.myProperty); 295 | } 296 | 297 | @Mapper() 298 | abstract class NestedMapper { 299 | NestedTarget fromModel(NestedSource model); 300 | 301 | SubNestedTarget fromSubClassModel(SubNestedSource model); 302 | } 303 | ``` 304 | 305 | Will generate the mapper 306 | 307 | ```dart 308 | // nestedmapper.mapper.g.dart 309 | class NestedMapperImpl extends NestedMapper { 310 | @override 311 | NestedTarget fromModel(NestedSource model) { 312 | final nestedtarget = NestedTarget(fromSubClassModel(model.subNested)); 313 | return nestedtarget; 314 | } 315 | 316 | @override 317 | SubNestedTarget fromSubClassModel(SubNestedSource model) { 318 | final subnestedtarget = SubNestedTarget(model.myProperty); 319 | return subnestedtarget; 320 | } 321 | } 322 | 323 | ``` 324 | 325 | Alternatively you can directly define the nested mapping in the source attribute. 326 | 327 | ```dart 328 | class User { 329 | final String username; 330 | final String zipcode; 331 | final String street; 332 | 333 | User(this.username, this.zipcode, this.street); 334 | } 335 | 336 | class UserResponse { 337 | final String username; 338 | final AddressResponse address; 339 | 340 | UserResponse(this.username, this.address); 341 | } 342 | 343 | class AddressResponse { 344 | final String zipcode; 345 | final StreetResponse street; 346 | 347 | AddressResponse(this.zipcode, this.street); 348 | } 349 | 350 | class StreetResponse { 351 | final num streetNumber; 352 | final String streetName; 353 | 354 | StreetResponse(this.streetNumber, this.streetName); 355 | } 356 | ``` 357 | 358 | With this, you can define the mappings directly in the `Mapping` Annotation 359 | 360 | ```dart 361 | @Mapper() 362 | abstract class UserMapper { 363 | @Mapping(target: 'zipcode', source: 'response.address.zipcode') 364 | @Mapping(target: 'street', source: 'response.address.street.streetName') 365 | User fromResponse(UserResponse response); 366 | } 367 | ``` 368 | 369 | Would generate the following mapper. 370 | 371 | ```dart 372 | class UserMapperImpl extends UserMapper { 373 | UserMapperImpl() : super(); 374 | 375 | @override 376 | User fromResponse(UserResponse response) { 377 | final user = User(response.username, response.address.zipcode, 378 | response.address.street.streetName); 379 | return user; 380 | } 381 | } 382 | ``` 383 | 384 | ## List Support 385 | 386 | Lists will be mapped as new instances of a list, with help of the map method. 387 | 388 | ```dart 389 | class Source { 390 | final List intList; 391 | final List entryList; 392 | 393 | Source(this.intList, this.entryList); 394 | } 395 | 396 | class SourceEntry { 397 | final String prop; 398 | 399 | SourceEntry(this.prop); 400 | } 401 | 402 | class Target { 403 | final List intList; 404 | final List entryList; 405 | 406 | Target(this.intList, this.entryList); 407 | } 408 | 409 | class TargetEntry { 410 | final String prop; 411 | 412 | TargetEntry(this.prop); 413 | } 414 | 415 | @Mapper() 416 | abstract class ListMapper { 417 | Target fromSource(Source source); 418 | 419 | TargetEntry fromSourceEntry(SourceEntry source); 420 | } 421 | ``` 422 | 423 | Will generate the Mapper 424 | 425 | ```dart 426 | class ListMapperImpl extends ListMapper { 427 | @override 428 | Target fromSource(Source source) { 429 | final target = Target( 430 | source.intList.map((e) => e).toList(), 431 | source.entryList.map(fromSourceEntry).toList()); 432 | return target; 433 | } 434 | 435 | @override 436 | TargetEntry fromSourceEntry(SourceEntry source) { 437 | final targetentry = TargetEntry(source.prop); 438 | return targetentry; 439 | } 440 | } 441 | ``` 442 | 443 | ## Injectable 444 | 445 | The Mapper can be made a lazy injectable singleton, by setting the argument _useInjection_ to true, in the Mapper 446 | Interface. In this case you also need to add the injectable dependency, as described 447 | here. https://pub.dev/packages/injectable 448 | 449 | Make sure, that in the Mapper File, you import the injectable dependency, before running the build_runner! 450 | 451 | ```dart 452 | // dogmapper.dart 453 | 454 | import 'package:injectable/injectable.dart'; 455 | 456 | @Mapper(useInjectable = true) 457 | abstract class DogMapper { 458 | Dog fromModel(DogModel model); 459 | } 460 | ``` 461 | 462 | ```dart 463 | // dogmapper.mapper.g.dart 464 | @LazySingleton(as: DogMapper) 465 | class DogMapperImpl extends DogMapper { 466 | ... 467 | } 468 | ``` 469 | 470 | ## Freezed 471 | 472 | Generally you can use smartstruct with [freezed](https://pub.dev/packages/freezed). 473 | 474 | One problem you will have to manually workaround is ignoring the freezed generated `copyWith` method in the generated 475 | mapper. The copyWith field is a normal field in the model / entity, and smartstruct does not have a way of knowing on 476 | when to filter it out, and when not. 477 | 478 | Imagine having the following freezed models. 479 | 480 | ```dart 481 | @freezed 482 | class Dog with _$Dog { 483 | Dog._(); 484 | 485 | factory Dog(String name) = _Dog; 486 | } 487 | 488 | @freezed 489 | class DogModel with _$DogModel { 490 | factory DogModel(String name) = _DogModel; 491 | } 492 | ``` 493 | 494 | Freezed will generate a `copyWith` field for your `Dog` and `DogModel`. 495 | 496 | When generating the mapper, you explicitly have to ignore this field. 497 | 498 | ```dart 499 | @Mapper() 500 | abstract class DogMapper { 501 | @Mapping(target: 'copyWith', ignore: true) 502 | Dog fromModel(DogModel model); 503 | } 504 | ``` 505 | 506 | Will generate the mapper, using the factory constructor. 507 | 508 | ```dart 509 | class DogMapperImpl extends DogMapper { 510 | DogMapperImpl() : super(); 511 | 512 | @override 513 | Dog fromModel(DogModel model) { 514 | final dog = Dog(model.name); 515 | return freezedtarget; 516 | } 517 | } 518 | ``` 519 | 520 | ## Static Mapping 521 | 522 | Static Methods in a Mapper Class will automatically be mapped with a static pendant in the generated mapper file. 523 | 524 | ```dart 525 | class Dog { 526 | final String name; 527 | 528 | Dog(this.name); 529 | } 530 | 531 | class DogModel { 532 | final String name; 533 | 534 | DogModel(this.name); 535 | } 536 | ``` 537 | 538 | ```dart 539 | @Mapper() 540 | class DogMapper { 541 | static Dog fromModel(DogModel model) => _$fromModel(model); 542 | } 543 | ``` 544 | 545 | Will generate a mapper file providing the following static methods. 546 | 547 | ```dart 548 | Dog _$fromModel(DogModel model) { 549 | final dog = Dog(model.name); 550 | return dog; 551 | } 552 | ``` 553 | 554 | ### Ignoring certain static Mapping 555 | 556 | Smartstruct will generate a mapping method for every static method in the mapper class. This can and will get in 557 | conflict with the functional mapping, where we define certain functions for custom mapping. 558 | 559 | Most of these custom mappings will be ignored by default, if it is a primitive type, a list, set, or an enum. In other 560 | cases the method has to be explicitly ignored, so it will not generate a mapping method, where I do not want one. 561 | 562 | ```dart 563 | 564 | class Breed { 565 | final String name; 566 | Breed(this.name); 567 | } 568 | 569 | class Dog { 570 | final String name; 571 | final Breed breed; 572 | 573 | Dog(this.name, this.breed); 574 | } 575 | 576 | class DogModel { 577 | final String name; 578 | 579 | DogModel(this.name); 580 | } 581 | ``` 582 | 583 | ```dart 584 | @Mapper() 585 | class DogMapper { 586 | 587 | @IgnoreMapping() 588 | static Breed customBreed() => Breed('Chihuahua'); 589 | 590 | @Mapping(target: 'breed', source: customBreed) 591 | static Dog fromModel(DogModel model) => _$fromModel(model); 592 | } 593 | ``` 594 | 595 | Smartstruct can not differentiate between if he should generate a mapping method for `customBreed` or not. 596 | So we have to tell him explicitly to not generate a mapping method by adding the Annotation `@IgnoreMapping` 597 | 598 | ## Static Mapping with a proxy 599 | 600 | Alternatively you can set ``generateStaticProxy`` to ``true``in the Mapping Annotation, to generate a Mapper Proxy 601 | implementation for your static methods. 602 | 603 | ```dart 604 | class Dog { 605 | final String name; 606 | 607 | Dog(this.name); 608 | } 609 | 610 | class DogModel { 611 | final String name; 612 | 613 | DogModel(this.name); 614 | } 615 | ``` 616 | 617 | ```dart 618 | @Mapper(generateStaticProxy = true) 619 | class DogMapper { 620 | Dog fromModel(DogModel model); 621 | } 622 | ``` 623 | 624 | Will generate the following mapper. 625 | 626 | ```dart 627 | class DogMapperImpl extends DogMapper { 628 | DogMapperImpl() : super(); 629 | 630 | @override 631 | Dog fromModel(DogModel model) { 632 | final dog = Dog(model.name); 633 | return dog; 634 | } 635 | } 636 | 637 | class DogMapper$ { 638 | static final DogMapper mapper = DogMapperImpl(); 639 | 640 | static Dog fromModel(DogModel model) => 641 | mapper.fromModel(model); 642 | } 643 | ``` 644 | 645 | # Examples 646 | 647 | Please refer to the [example](https://github.com/smotastic/smartstruct/tree/master/example) package, for a list of 648 | examples and how to use the Mapper Annotation. 649 | 650 | You can always run the examples by navigating to the examples package and executing the generator. 651 | 652 | ```console 653 | $ dart pub get 654 | ... 655 | $ dart run build_runner build 656 | ``` 657 | 658 | # Roadmap 659 | 660 | Feel free to open a [Pull Request](https://github.com/smotastic/smartstruct/pulls), if you'd like to contribute. 661 | 662 | Or just open an issue, and i do my level best to deliver. 663 | --------------------------------------------------------------------------------