{"id":29591696,"url":"https://github.com/smotastic/smartstruct","last_synced_at":"2025-07-20T05:10:15.407Z","repository":{"id":37814790,"uuid":"373180170","full_name":"smotastic/smartstruct","owner":"smotastic","description":"Dart Code Generator for generating mapper classes","archived":false,"fork":false,"pushed_at":"2023-02-27T15:43:48.000Z","size":224,"stargazers_count":34,"open_issues_count":15,"forks_count":17,"subscribers_count":4,"default_branch":"develop","last_synced_at":"2023-09-14T22:41:12.561Z","etag":null,"topics":["annotation-processor","beanmapper","codegenerator","dart","hacktoberfest","mapper","mapstruct","nullsafe","object-mapper"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smotastic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-02T13:35:36.000Z","updated_at":"2023-08-31T21:44:36.000Z","dependencies_parsed_at":"2023-02-19T01:15:57.936Z","dependency_job_id":null,"html_url":"https://github.com/smotastic/smartstruct","commit_stats":null,"previous_names":[],"tags_count":11,"template":null,"template_full_name":null,"purl":"pkg:github/smotastic/smartstruct","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smotastic%2Fsmartstruct","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smotastic%2Fsmartstruct/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smotastic%2Fsmartstruct/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smotastic%2Fsmartstruct/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smotastic","download_url":"https://codeload.github.com/smotastic/smartstruct/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smotastic%2Fsmartstruct/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266070972,"owners_count":23871922,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["annotation-processor","beanmapper","codegenerator","dart","hacktoberfest","mapper","mapstruct","nullsafe","object-mapper"],"created_at":"2025-07-20T05:04:00.366Z","updated_at":"2025-07-20T05:10:15.393Z","avatar_url":"https://github.com/smotastic.png","language":"Dart","readme":"# Smartstruct - Dart bean mappings - the easy nullsafe way!\n\nCode generator for generating type-safe mappers in dart, inspired by https://mapstruct.org/\n\n- [Installation](#installation)\n- [Usage](#usage)\n- [Examples](#examples)\n- [Roadmap](#roadmap)\n\n# Overview\n\n- Add smartstruct as a dependency, and smartstruct_generator as a dev_dependency\n- Create a Mapper class\n- Annotate the class with @mapper\n- Run the build_runner\n- Use the generated Mapper!\n\n# Installation\n\nAdd smartstruct as a dependency, and the generator as a dev_dependency.\n\nhttps://pub.dev/packages/smartstruct\n\n```yaml\ndependencies:\n  smartstruct: [ version ]\n\ndev_dependencies:\n  smartstruct_generator: [ version ]\n  # add build runner if not already added\n  build_runner:\n```\n\nRun the generator\n\n```console\ndart run build_runner build\nflutter packages pub run build_runner build\n// or watch\nflutter packages pub run build_runner watch\n```\n\n# Usage\n\nCreate your beans.\n\n```dart\nclass Dog {\n  final String breed;\n  final int age;\n  final String name;\n\n  Dog(this.breed, this.age, this.name);\n}\n```\n\n```dart\nclass DogModel {\n  final String breed;\n  final int age;\n  final String name;\n\n  DogModel(this.breed, this.age, this.name);\n}\n```\n\nTo generate a mapper for these two beans, you need to create a mapper interface.\n\n```dart\n// dogmapper.dart\npart 'dogmapper.mapper.g.dart';\n\n@Mapper()\nabstract class DogMapper {\n  Dog fromModel(DogModel model);\n}\n```\n\nOnce you ran the generator, next to your _dog.mapper.dart_ a _dog.mapper.g.dart_ will be generated.\n\n```\ndart run build_runner build\n```\n\n```dart\n// dogmapper.mapper.g.dart\nclass DogMapperImpl extends DogMapper {\n  @override\n  Dog fromModel(DogModel model) {\n    Dog dog = Dog(model.breed, model.age, model.name);\n    return dog;\n  }\n}\n```\n\nThe Mapper supports positional arguments, named arguments and property access via implicit and explicit setters.\n\n## Case sensitivity\n\nBy default mapper generator works in case insensitivity manner.\n\n```dart\nclass Source {\n  final String userName;\n\n  Source(this.userName);\n}\n\nclass Target {\n  final String username;\n\n  Target({required this.username});\n}\n\n@Mapper()\nabstract class ExampleMapper {\n  Target fromSource(Source source);\n}\n```\n\nAs you can see, classes above got different field's names (case) for username. Because mappers are case insensitive by\ndefault, those classes are correctly mapped.\n\n```dart\n\nclass ExampleMapperImpl extends ExampleMapper {\n  @override\n  Target fromSource(Source source) {\n    final target = Target(username: source.userName);\n    return target;\n  }\n}\n```\n\nTo create case sensitive mapper, you can add param caseSensitiveFields to @Mapper annotation. Case sensitive mapper is\nchecking field's names in case sensitive manner.\n\n```dart\n\n@Mapper(caseSensitiveFields: true)\nabstract class ExampleMapper {\n  Target fromSource(Source source);\n}\n```\n\n## Explicit Field Mapping\n\nIf some fields do not match each other, you can add a Mapping Annotation on the method level, to change the behaviour of\ncertain mappings.\n\n```dart\nclass Dog {\n  final String name;\n\n  Dog(this.name);\n}\n\nclass DogModel {\n  final String dogName;\n\n  DogModel(this.dogName);\n}\n```\n\n```dart\n@Mapper()\nclass DogMapper {\n  @Mapping(source: 'dogName', target: 'name')\n  Dog fromModel(DogModel model);\n}\n```\n\nIn this case, the field _dogName_ of _DogModel_ will be mapped to the field _name_ of the resulting _Dog_\n\n```dart\nclass DogMapperImpl extends DogMapper {\n  @override\n  Dog fromModel(DogModel model) {\n    Dog dog = Dog(model.dogName);\n    return dog;\n  }\n}\n```\n\n### Function Mapping\n\nThe source attribute can also be a Function. This Function will then be called with the Source Parameter of the mapper\nmethod as a parameter.\n\n```dart\nclass Dog {\n  final String name;\n  final String breed;\n\n  Dog(this.name, this.breed);\n}\n\nclass DogModel {\n  final String name;\n\n  DogModel(this.name);\n}\n```\n\n```dart\n@Mapper()\nclass DogMapper {\n  static String randomBreed(DogModel model) =\u003e 'some random breed';\n\n  @Mapping(source: randomBreed, target: 'breed')\n  Dog fromModel(DogModel model);\n}\n```\n\nWill generate the following Mapper.\n\n```dart\nclass DogMapperImpl extends DogMapper {\n  @override\n  Dog fromModel(DogModel model) {\n    Dog dog = Dog(model.dogName, DogMapper.randomBreed(model));\n    return dog;\n  }\n}\n```\n\n### Ignore Fields\n\nFields can be ignored, by specififying the `ignore` attribute on the Mapping `Annotation``\n\n```dart\nclass Dog {\n  final String name;\n  String? breed;\n\n  Dog(this.name);\n}\n\nclass DogModel {\n  final String name;\n  final String breed;\n\n  DogModel(this.name, this.breed);\n}\n```\n\n```dart\n@Mapper()\nclass DogMapper {\n  @Mapping(target: 'breed', ignore: true)\n  Dog fromModel(DogModel model);\n}\n```\n\nWill generate the following Mapper.\n\n```dart\nclass DogMapperImpl extends DogMapper {\n  @override\n  Dog fromModel(DogModel model) {\n    Dog dog = Dog(model.name);\n    return dog;\n  }\n}\n```\n\n## Nested Bean Mapping\n\nNested beans can be mapped, by defining an additional mapper method for the nested bean.\n\n```dart\n// nestedmapper.dart\nclass NestedTarget {\n  final SubNestedTarget subNested;\n\n  NestedTarget(this.subNested);\n}\n\nclass SubNestedTarget {\n  final String myProperty;\n\n  SubNestedTarget(this.myProperty);\n}\n\nclass NestedSource {\n  final SubNestedSource subNested;\n\n  NestedSource(this.subNested);\n}\n\nclass SubNestedSource {\n  final String myProperty;\n\n  SubNestedSource(this.myProperty);\n}\n\n@Mapper()\nabstract class NestedMapper {\n  NestedTarget fromModel(NestedSource model);\n\n  SubNestedTarget fromSubClassModel(SubNestedSource model);\n}\n```\n\nWill generate the mapper\n\n```dart\n// nestedmapper.mapper.g.dart\nclass NestedMapperImpl extends NestedMapper {\n  @override\n  NestedTarget fromModel(NestedSource model) {\n    final nestedtarget = NestedTarget(fromSubClassModel(model.subNested));\n    return nestedtarget;\n  }\n\n  @override\n  SubNestedTarget fromSubClassModel(SubNestedSource model) {\n    final subnestedtarget = SubNestedTarget(model.myProperty);\n    return subnestedtarget;\n  }\n}\n\n```\n\nAlternatively you can directly define the nested mapping in the source attribute.\n\n```dart\nclass User {\n  final String username;\n  final String zipcode;\n  final String street;\n\n  User(this.username, this.zipcode, this.street);\n}\n\nclass UserResponse {\n  final String username;\n  final AddressResponse address;\n\n  UserResponse(this.username, this.address);\n}\n\nclass AddressResponse {\n  final String zipcode;\n  final StreetResponse street;\n\n  AddressResponse(this.zipcode, this.street);\n}\n\nclass StreetResponse {\n  final num streetNumber;\n  final String streetName;\n\n  StreetResponse(this.streetNumber, this.streetName);\n}\n```\n\nWith this, you can define the mappings directly in the `Mapping` Annotation\n\n```dart\n@Mapper()\nabstract class UserMapper {\n  @Mapping(target: 'zipcode', source: 'response.address.zipcode')\n  @Mapping(target: 'street', source: 'response.address.street.streetName')\n  User fromResponse(UserResponse response);\n}\n```\n\nWould generate the following mapper.\n\n```dart\nclass UserMapperImpl extends UserMapper {\n  UserMapperImpl() : super();\n\n  @override\n  User fromResponse(UserResponse response) {\n    final user = User(response.username, response.address.zipcode,\n        response.address.street.streetName);\n    return user;\n  }\n}\n```\n\n## List Support\n\nLists will be mapped as new instances of a list, with help of the map method.\n\n```dart\nclass Source {\n  final List\u003cint\u003e intList;\n  final List\u003cSourceEntry\u003e entryList;\n\n  Source(this.intList, this.entryList);\n}\n\nclass SourceEntry {\n  final String prop;\n\n  SourceEntry(this.prop);\n}\n\nclass Target {\n  final List\u003cint\u003e intList;\n  final List\u003cTargetEntry\u003e entryList;\n\n  Target(this.intList, this.entryList);\n}\n\nclass TargetEntry {\n  final String prop;\n\n  TargetEntry(this.prop);\n}\n\n@Mapper()\nabstract class ListMapper {\n  Target fromSource(Source source);\n\n  TargetEntry fromSourceEntry(SourceEntry source);\n}\n```\n\nWill generate the Mapper\n\n```dart\nclass ListMapperImpl extends ListMapper {\n  @override\n  Target fromSource(Source source) {\n    final target = Target(\n        source.intList.map((e) =\u003e e).toList(),\n        source.entryList.map(fromSourceEntry).toList());\n    return target;\n  }\n\n  @override\n  TargetEntry fromSourceEntry(SourceEntry source) {\n    final targetentry = TargetEntry(source.prop);\n    return targetentry;\n  }\n}\n```\n\n## Injectable\n\nThe Mapper can be made a lazy injectable singleton, by setting the argument _useInjection_ to true, in the Mapper\nInterface. In this case you also need to add the injectable dependency, as described\nhere. https://pub.dev/packages/injectable\n\nMake sure, that in the Mapper File, you import the injectable dependency, before running the build_runner!\n\n```dart\n// dogmapper.dart\n\nimport 'package:injectable/injectable.dart';\n\n@Mapper(useInjectable = true)\nabstract class DogMapper {\n  Dog fromModel(DogModel model);\n}\n```\n\n```dart\n// dogmapper.mapper.g.dart\n@LazySingleton(as: DogMapper)\nclass DogMapperImpl extends DogMapper {\n  ...\n}\n```\n\n## Freezed\n\nGenerally you can use smartstruct with [freezed](https://pub.dev/packages/freezed).\n\nOne problem you will have to manually workaround is ignoring the freezed generated `copyWith` method in the generated\nmapper. The copyWith field is a normal field in the model / entity, and smartstruct does not have a way of knowing on\nwhen to filter it out, and when not.\n\nImagine having the following freezed models.\n\n```dart\n@freezed\nclass Dog with _$Dog {\n  Dog._();\n\n  factory Dog(String name) = _Dog;\n}\n\n@freezed\nclass DogModel with _$DogModel {\n  factory DogModel(String name) = _DogModel;\n}\n```\n\nFreezed will generate a `copyWith` field for your `Dog` and `DogModel`.\n\nWhen generating the mapper, you explicitly have to ignore this field.\n\n```dart\n@Mapper()\nabstract class DogMapper {\n  @Mapping(target: 'copyWith', ignore: true)\n  Dog fromModel(DogModel model);\n}\n```\n\nWill generate the mapper, using the factory constructor.\n\n```dart\nclass DogMapperImpl extends DogMapper {\n  DogMapperImpl() : super();\n\n  @override\n  Dog fromModel(DogModel model) {\n    final dog = Dog(model.name);\n    return freezedtarget;\n  }\n}\n```\n\n## Static Mapping\n\nStatic Methods in a Mapper Class will automatically be mapped with a static pendant in the generated mapper file.\n\n```dart\nclass Dog {\n  final String name;\n\n  Dog(this.name);\n}\n\nclass DogModel {\n  final String name;\n\n  DogModel(this.name);\n}\n```\n\n```dart\n@Mapper()\nclass DogMapper {\n  static Dog fromModel(DogModel model) =\u003e _$fromModel(model);\n}\n```\n\nWill generate a mapper file providing the following static methods.\n\n```dart\nDog _$fromModel(DogModel model) {\n  final dog = Dog(model.name);\n  return dog;\n}\n```\n\n### Ignoring certain static Mapping\n\nSmartstruct will generate a mapping method for every static method in the mapper class. This can and will get in\nconflict with the functional mapping, where we define certain functions for custom mapping.\n\nMost of these custom mappings will be ignored by default, if it is a primitive type, a list, set, or an enum. In other\ncases the method has to be explicitly ignored, so it will not generate a mapping method, where I do not want one.\n\n```dart\n\nclass Breed {\n  final String name;\n  Breed(this.name);\n}\n\nclass Dog {\n  final String name;\n  final Breed breed;\n\n  Dog(this.name, this.breed);\n}\n\nclass DogModel {\n  final String name;\n\n  DogModel(this.name);\n}\n```\n\n```dart\n@Mapper()\nclass DogMapper {\n  \n  @IgnoreMapping()\n  static Breed customBreed() =\u003e Breed('Chihuahua');\n  \n  @Mapping(target: 'breed', source: customBreed)  \n  static Dog fromModel(DogModel model) =\u003e _$fromModel(model);\n}\n```\n\nSmartstruct can not differentiate between if he should generate a mapping method for `customBreed` or not.\nSo we have to tell him explicitly to not generate a mapping method by adding the Annotation `@IgnoreMapping`\n\n## Static Mapping with a proxy\n\nAlternatively you can set ``generateStaticProxy`` to ``true``in the Mapping Annotation, to generate a Mapper Proxy\nimplementation for your static methods.\n\n```dart\nclass Dog {\n  final String name;\n\n  Dog(this.name);\n}\n\nclass DogModel {\n  final String name;\n\n  DogModel(this.name);\n}\n```\n\n```dart\n@Mapper(generateStaticProxy = true)\nclass DogMapper {\n  Dog fromModel(DogModel model);\n}\n```\n\nWill generate the following mapper.\n\n```dart\nclass DogMapperImpl extends DogMapper {\n  DogMapperImpl() : super();\n\n  @override\n  Dog fromModel(DogModel model) {\n    final dog = Dog(model.name);\n    return dog;\n  }\n}\n\nclass DogMapper$ {\n  static final DogMapper mapper = DogMapperImpl();\n\n  static Dog fromModel(DogModel model) =\u003e\n      mapper.fromModel(model);\n}\n```\n\n# Examples\n\nPlease refer to the [example](https://github.com/smotastic/smartstruct/tree/master/example) package, for a list of\nexamples and how to use the Mapper Annotation.\n\nYou can always run the examples by navigating to the examples package and executing the generator.\n\n```console\n$ dart pub get\n...\n$ dart run build_runner build\n```\n\n# Roadmap\n\nFeel free to open a [Pull Request](https://github.com/smotastic/smartstruct/pulls), if you'd like to contribute.\n\nOr just open an issue, and i do my level best to deliver.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmotastic%2Fsmartstruct","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmotastic%2Fsmartstruct","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmotastic%2Fsmartstruct/lists"}