All Projects → smotastic → smartstruct

smotastic / smartstruct

Licence: other
Dart Code Generator for generating mapper classes

Programming Languages

dart
5743 projects

Projects that are alternatives of or similar to smartstruct

PropMapper
Object mapper for .NET. Flat and basic, but FAST.
Stars: ✭ 30 (+50%)
Mutual labels:  mapper, object-mapper
Mapstruct Kotlin
Using mapstruct with kotlin data classes.
Stars: ✭ 84 (+320%)
Mutual labels:  mapper, annotation-processor
Mapstruct
An annotation processor for generating type-safe bean mappers
Stars: ✭ 4,710 (+23450%)
Mutual labels:  annotation-processor, mapstruct
Venflow
A brand new, fast and lightweight ORM, build for PostgreSQL.
Stars: ✭ 162 (+710%)
Mutual labels:  mapper, object-mapper
Odapter
C# code generator for Oracle packages
Stars: ✭ 16 (-20%)
Mutual labels:  codegenerator
Detached-Mapper
An ORM friendly mapper. Allows saving entire entity graphs. Heavily inspired in GraphDiff and AutoMapper.
Stars: ✭ 89 (+345%)
Mutual labels:  mapper
piri
Piri is a lightweight annotation processing library that generates static factory methods for your Activities and Fragments.
Stars: ✭ 53 (+165%)
Mutual labels:  annotation-processor
AnnotationProcessing
✔️ㅤ[ARTICLE] Writing your own Annotation Processors in Android
Stars: ✭ 47 (+135%)
Mutual labels:  annotation-processor
Kodgen
C++17 parser and code generator
Stars: ✭ 19 (-5%)
Mutual labels:  codegenerator
RecordParser
Zero Allocation Writer/Reader Parser for .NET Core
Stars: ✭ 155 (+675%)
Mutual labels:  mapper
Overseer
Tool for analyzing Starcraft 2 maps by region decomposition
Stars: ✭ 13 (-35%)
Mutual labels:  mapper
awesome-maps-ukraine
A curated list of maps of Ukraine, ukrainian mappers and tools that they use or develop for creating and publish maps
Stars: ✭ 35 (+75%)
Mutual labels:  mapper
jtsgen
Convert Java Types to TypeScript
Stars: ✭ 34 (+70%)
Mutual labels:  annotation-processor
php-test-generator
Generate test cases for existing PHP files
Stars: ✭ 47 (+135%)
Mutual labels:  codegenerator
seezoon-framework-all
Seezoon快速开发平台基于spring mybatis shiro jquery 完全的前后端分离的后台管理系统,采用最简单技术,实现快速开发.
Stars: ✭ 47 (+135%)
Mutual labels:  codegenerator
AnnotationProcessorStarter
Project to set up basics of a Java annotation processor
Stars: ✭ 19 (-5%)
Mutual labels:  annotation-processor
enumer
A Go tool to auto generate methods for your enums
Stars: ✭ 167 (+735%)
Mutual labels:  codegenerator
Stitch
Simple threading library using annotations for Android
Stars: ✭ 28 (+40%)
Mutual labels:  annotation-processor
AlamofireMapper
Mapper for Alamofire use Swift 4 decoable
Stars: ✭ 18 (-10%)
Mutual labels:  mapper
g910-gkey-macro-support
GKey support for Logitech G910 Keyboard on Linux
Stars: ✭ 85 (+325%)
Mutual labels:  mapper

Smartstruct - Dart bean mappings - the easy nullsafe way!

Code generator for generating type-safe mappers in dart, inspired by https://mapstruct.org/

Overview

  • Add smartstruct as a dependency, and smartstruct_generator as a dev_dependency
  • Create a Mapper class
  • Annotate the class with @mapper
  • Run the build_runner
  • Use the generated Mapper!

Installation

Add smartstruct as a dependency, and the generator as a dev_dependency.

https://pub.dev/packages/smartstruct

dependencies:
  smartstruct: [version]

dev_dependencies:
  smartstruct_generator: [version]
  # add build runner if not already added
  build_runner:

Run the generator

dart run build_runner build
flutter packages pub run build_runner build
// or watch
flutter packages pub run build_runner watch

Usage

Create your beans.

class Dog {
    final String breed;
    final int age;
    final String name;
    Dog(this.breed, this.age, this.name);
}
class DogModel {
    final String breed;
    final int age;
    final String name;
    DogModel(this.breed, this.age, this.name);
}

To generate a mapper for these two beans, you need to create a mapper interface.

// dogmapper.dart
part 'dogmapper.mapper.g.dart';

@Mapper()
abstract class DogMapper {
    Dog fromModel(DogModel model);
}

Once you ran the generator, next to your dog.mapper.dart a dog.mapper.g.dart will be generated.

dart run build_runner build
// dogmapper.mapper.g.dart
class DogMapperImpl extends DogMapper {
    @override
    Dog fromModel(DogModel model) {
        Dog dog = Dog(model.breed, model.age, model.name);
        return dog;
    }
}

The Mapper supports positional arguments, named arguments and property access via implicit and explicit setters.

Case sensitivity

By default mapper generator works in case insensitivity manner.

class Source {
  final String userName;

  Source(this.userName);
}

class Target {
  final String username;

  Target({required this.username});
}

@Mapper()
abstract class ExampleMapper {
  Target fromSource(Source source);
}

As you can see, classes above got different field's names (case) for username. Because mappers are case insensitive by default, those classes are correctly mapped.

class ExampleMapperImpl extends ExampleMapper {
  @override
  Target fromSource(Source source) {
    final target = Target(username: source.userName);
    return target;
  }
}

To create case sensitive mapper, you can add param caseSensitiveFields to @Mapper annotation. Case sensitive mapper is checking field's names in case sensitive manner.

@Mapper(caseSensitiveFields: true)
abstract class ExampleMapper {
  Target fromSource(Source source);
}

Explicit Field Mapping

If some fields do not match each other, you can add a Mapping Annotation on the method level, to change the behaviour of certain mappings.

class Dog {
    final String name;
    Dog(this.name);
}
class DogModel {
    final String dogName;
    DogModel(this.dogName);
}
@Mapper()
class DogMapper {
    @Mapping(source: 'dogName', target: 'name')
    Dog fromModel(DogModel model);
}

In this case, the field dogName of DogModel will be mapped to the field name of the resulting Dog

class DogMapperImpl extends DogMapper {
    @override
    Dog fromModel(DogModel model) {
        Dog dog = Dog(model.dogName);
        return dog;
    }
}

Function Mapping

The source attribute can also be a Function. This Function will then be called with the Source Parameter of the mapper method as a parameter.

class Dog {
    final String name;
    final String breed;
    Dog(this.name, this.breed);
}
class DogModel {
    final String name;
    DogModel(this.name);
}
@Mapper()
class DogMapper {
    static String randomBreed(DogModel model) => 'some random breed';

    @Mapping(source: randomBreed, target: 'breed')
    Dog fromModel(DogModel model);
}

Will generate the following Mapper.

class DogMapperImpl extends DogMapper {
    @override
    Dog fromModel(DogModel model) {
        Dog dog = Dog(model.dogName, DogMapper.randomBreed(model));
        return dog;
    }
}

Ignore Fields

Fields can be ignored, by specififying the ignore attribute on the Mapping `Annotation``

class Dog {
    final String name;
    String? breed;
    Dog(this.name);
}
class DogModel {
    final String name;
    final String breed;
    DogModel(this.name, this.breed);
}
@Mapper()
class DogMapper {
    @Mapping(target: 'breed', ignore: true)
    Dog fromModel(DogModel model);
}

Will generate the following Mapper.

class DogMapperImpl extends DogMapper {
    @override
    Dog fromModel(DogModel model) {
        Dog dog = Dog(model.name);
        return dog;
    }
}

Nested Bean Mapping

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

// nestedmapper.dart
class NestedTarget {
  final SubNestedTarget subNested;
  NestedTarget(this.subNested);
}
class SubNestedTarget {
  final String myProperty;
  SubNestedTarget(this.myProperty);
}

class NestedSource {
  final SubNestedSource subNested;
  NestedSource(this.subNested);
}

class SubNestedSource {
  final String myProperty;
  SubNestedSource(this.myProperty);
}

@Mapper()
abstract class NestedMapper {
  NestedTarget fromModel(NestedSource model);

  SubNestedTarget fromSubClassModel(SubNestedSource model);
}

Will generate the mapper

// nestedmapper.mapper.g.dart
class NestedMapperImpl extends NestedMapper {
  @override
  NestedTarget fromModel(NestedSource model) {
    final nestedtarget = NestedTarget(fromSubClassModel(model.subNested));
    return nestedtarget;
  }

  @override
  SubNestedTarget fromSubClassModel(SubNestedSource model) {
    final subnestedtarget = SubNestedTarget(model.myProperty);
    return subnestedtarget;
  }
}

Alternatively you can directly define the nested mapping in the source attribute.

class User {
  final String username;
  final String zipcode;
  final String street;

  User(this.username, this.zipcode, this.street);
}

class UserResponse {
  final String username;
  final AddressResponse address;

  UserResponse(this.username, this.address);
}

class AddressResponse {
  final String zipcode;
  final StreetResponse street;

  AddressResponse(this.zipcode, this.street);
}

class StreetResponse {
  final num streetNumber;
  final String streetName;

  StreetResponse(this.streetNumber, this.streetName);
}

With this, you can define the mappings directly in the Mapping Annotation

@Mapper()
abstract class UserMapper {
  @Mapping(target: 'zipcode', source: 'response.address.zipcode')
  @Mapping(target: 'street', source: 'response.address.street.streetName')
  User fromResponse(UserResponse response);
}

Would generate the following mapper.

class UserMapperImpl extends UserMapper {
  UserMapperImpl() : super();

  @override
  User fromResponse(UserResponse response) {
    final user = User(response.username, response.address.zipcode,
        response.address.street.streetName);
    return user;
  }
}

List Support

Lists will be mapped as new instances of a list, with help of the map method.

class Source {
  final List<int> intList;
  final List<SourceEntry> entryList;

  Source(this.intList, this.entryList);
}

class SourceEntry {
  final String prop;

  SourceEntry(this.prop);
}

class Target {
  final List<int> intList;
  final List<TargetEntry> entryList;

  Target(this.intList, this.entryList);
}

class TargetEntry {
  final String prop;

  TargetEntry(this.prop);
}

@Mapper()
abstract class ListMapper {
  Target fromSource(Source source);
  TargetEntry fromSourceEntry(SourceEntry source);
}

Will generate the Mapper

class ListMapperImpl extends ListMapper {
  @override
  Target fromSource(Source source) {
    final target = Target(
      source.intList.map((e) => e).toList(),
      source.entryList.map(fromSourceEntry).toList());
    return target;
  }

  @override
  TargetEntry fromSourceEntry(SourceEntry source) {
    final targetentry = TargetEntry(source.prop);
    return targetentry;
  }
}

Injectable

The Mapper can be made a lazy injectable singleton, by setting the argument useInjection to true, in the Mapper Interface. In this case you also need to add the injectable dependency, as described here. https://pub.dev/packages/injectable

Make sure, that in the Mapper File, you import the injectable dependency, before running the build_runner!

// dogmapper.dart

import 'package:injectable/injectable.dart';

@Mapper(useInjectable = true)
abstract class DogMapper {
    Dog fromModel(DogModel model);
}
// dogmapper.mapper.g.dart
@LazySingleton(as: DogMapper)
class DogMapperImpl extends DogMapper {...}

Freezed

Generally you can use smartstruct with freezed.

One problem you will have to manually workaround is ignoring the freezed generated copyWith method in the generated mapper. 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.

Imagine having the following freezed models.

@freezed
class Dog with _$Dog {
  Dog._();
  factory Dog(String name) = _Dog;
}

@freezed
class DogModel with _$DogModel {
  factory DogModel(String name) = _DogModel;
}

Freezed will generate a copyWith field for your Dog and DogModel.

When generating the mapper, you explicitly have to ignore this field.

@Mapper()
abstract class DogMapper {
  @Mapping(target: 'copyWith', ignore: true)
  Dog fromModel(DogModel model);
}

Will generate the mapper, using the factory constructor.

class DogMapperImpl extends DogMapper {
  DogMapperImpl() : super();

  @override
  Dog fromModel(DogModel model) {
    final dog = Dog(model.name);
    return freezedtarget;
  }
}

Examples

Please refer to the example package, for a list of examples and how to use the Mapper Annotation.

You can always run the examples by navigating to the examples package and executing the generator.

$ dart pub get
...
$ dart run build_runner build

Roadmap

Feel free to open a Pull Request, if you'd like to contribute.

Or just open an issue, and i do my level best to deliver.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].