Smartstruct - Dart bean映射 - 轻松实现空安全!

用于生成类型安全映射器的代码生成器,灵感来自 https://mapstruct.org/

概述

  • 将 smartstruct 添加为依赖项,并将 smartstruct_generator 添加为 dev_dependency
  • 创建一个 Mapper 类
  • 使用 @mapper 注释该类
  • 运行 build_runner
  • 使用生成的 Mapper!

安装

将 smartstruct 添加为依赖项,并将生成器添加为 dev_dependency。

https://pub.dev/packages/smartstruct

dependencies:
  smartstruct: [version]

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

运行生成器

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

用法

创建您的 bean。

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

要为这两个 bean 生成映射器,您需要创建一个映射器接口。

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

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

运行生成器后,将在 dog.mapper.dart 旁边生成 dog.mapper.g.dart

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

Mapper 支持位置参数、命名参数以及通过隐式和显式 setter 进行属性访问。

大小写敏感性

默认情况下,mapper 生成器以不区分大小写的方式工作。

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

如您所见,上面的类对于 username 有不同的字段名称(大小写)。由于映射器默认不区分大小写,因此这些类被正确映射。


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

要创建区分大小写的映射器,可以在 @Mapper 注释中添加 param caseSensitiveFields。区分大小写的映射器会以区分大小写的方式检查字段名称。


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

显式字段映射

如果某些字段不匹配,可以在方法级别添加 Mapping 注释,以更改特定映射的行为。

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

在这种情况下,DogModeldogName 字段将被映射到生成的 Dogname 字段。

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

函数映射

source 属性也可以是一个函数。然后将调用此函数,并将映射器方法的 Source 参数作为参数传递。

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

将生成以下 Mapper。

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

嵌套 Bean 映射

嵌套 bean 可以通过为嵌套 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);
}

将生成 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;
  }
}

列表支持

列表将通过 map 方法映射为新列表实例。

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

将生成 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;
  }
}

可注入

通过在 Mapper Interface 中将参数 useInjection 设置为 true,可以将 Mapper 制作为惰性可注入的单例。
在这种情况下,您还需要添加可注入依赖项,如下所述: https://pub.dev/packages/injectable

确保在运行 build_runner 之前,在 Mapper 文件中导入可注入的依赖项!

// 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 {...}

示例

有关示例列表和如何使用 Mapper Annotation,请参阅 example 包。

您可以通过导航到示例包并执行生成器来随时运行示例。

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

路线图

如果您想贡献,欢迎提交 Pull Request

或者直接打开一个 issue,我将尽力实现。

GitHub

https://github.com/smotastic/smartstruct