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);
}
在这种情况下,DogModel 的 dogName 字段将被映射到生成的 Dog 的 name 字段。
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,我将尽力实现。