Flutter实体序列化插件entity_serializer的使用
Flutter实体序列化插件entity_serializer的使用
entity_serializer
是一个用于简化数据类和序列化器构建过程的Flutter插件。该插件通过XML文件描述模型,生成相应的Dart代码,从而实现数据类的序列化和反序列化。
功能简介
解决的问题
在开发过程中,经常会遇到需要创建多个数据类(如客户端与服务器之间的JSON通信数据类,以及数据库存储的数据类)。使用 entity_serializer
可以减少重复的代码编写,提高开发效率。
如何工作
所有数据类应以简单的XML文件形式描述。在该文件中,定义了一个或多个可以操作数据类的序列化器。然后运行构建器或命令行工具生成 .dart
文件,这些文件可以在项目中使用。从这时起,就可以使用专用的序列化器对数据类进行序列化/反序列化。例如,对于 Foo
类,可以直接将其序列化为 Map<String, dynamic>
用于JSON和数据库存储,两种情况下可以有不同的序列化规则。整个过程无需中间DTO类,仅需扩展 List<dynamic>
和 Map<String, dynamic>
以及数据类即可。
限制
当前版本(及未来版本)不支持嵌套集合。例如,无法处理包含映射的列表或映射的映射等。如果需要此类功能,请将映射包装在一个自定义类型中。此外,该包将在满足所有需求前持续开发和扩展。
CLI 版本
存在 cli_main.dart
文件,它允许编译并使此工具成为独立应用,而不依赖于 build_runner
。要使用它,请调用带有 -h
参数来获取使用说明。通常情况下,你会调用:
cli_main -i data_model/list_test.xml -o sources/gen.dart
或者
cli_main -s -i data_model/list_test.xml -o sources/
XML 描述
有两种方法来描述模型:
- 所有内容都在一个文件中,使用
spec
根节点。 - 序列化器和实体分布在多个XML文件中。
多个XML文件(组合)
如果想将模型拆分为多个XML文件,可以按照以下步骤操作:
- 定义主XML文件,指向所有组件XML文件。
- 定义包含序列化器定义的XML文件。
- 定义包含实体定义的XML文件。
主XML文件
<composition>
<include file="comp_serializer.xml"/>
<include file="comp_entities_1.xml"/>
<include file="comp_entities_2.xml"/>
</composition>
序列化XML文件
根节点必须命名为 serializers
,且只能包含 serializer
或 serializerTemplate
节点。
实体XML文件
根节点必须命名为 entities
,且只能包含 class
、proxy
或 import
节点。
单一XML文件
以下是用于生成一切的XML格式的简要描述。我们来看一些例子和说明:
<?xml version="1.0"?>
<spec>
<import package="package:any_package.dart"/>
<serializer name="Ser1"/>
<class name="Book">
<string name="name"/>
</class>
</spec>
这是最简单的有意义的XML。我们定义了一个名为 Ser1
的序列化器和一个名为 Book
的数据类。预期 Book
类只包含一个名为 name
的 String
字段。为 Book
类生成序列化扩展。
导入节点
如果需要向生成的代码添加额外导入,可以使用 import
节点。该节点有一个强制属性 package
,用于指定需要导入的内容。每个XML可以有多个 import
节点。
序列化模板节点
serializerTemplate
节点几乎与 serializer
节点相同,主要区别在于构建器不会为 serializerTemplate
生成任何代码。它只是一个模板,供其他序列化器继承。每个序列化器可以通过 inheritFrom
属性继承 serializerTemplate
中的特殊化。
序列化节点
serializer
节点告知构建器需要为数据类生成命名序列化器。当前序列化器具有以下属性:
name
- 必须的,该名称作为代码生成的前缀,并且也可以放在class
节点的serializers
属性中。inheritFrom
- 可选,默认为空。逗号分隔的serializerTemplate
名称,从中复制特殊化。
默认情况下,序列化器可以处理 Dart 类型:int
、double
、String
、bool
以及在XML中描述的所有实体类。要进一步处理其他类的编码,可以通过序列化的子节点进行。详见下一节。
别名
每个类型的每种特殊化都可以有字段别名。为了在序列化/反序列化字段时使用新名称,只需添加以字段名称前缀 _
开头的属性,值是新名称。例如:
<spec>
<serializer name="KeepDate" >
<keep type="DateTime"/>
<keep type="int" _justInt="i"/>
</serializer>
<class name="TestEntity" copyWith="false">
<DateTime name="date"/>
<DateTime name="noAlias"/>
<int name="justInt"/>
</class>
</spec>
结果序列化器 KeepDate
对于类型 int
将所有名为 justInt
的字段重新映射为 i
。
保持
序列化器使用某些规则将 Dart 对象实例转换为 Map<String, dynamic>
。然而,有时你希望保留给定对象实例不变。因此,你想将对象直接复制到映射中。为此,存在 keep
节点,它仅包含一个 type
属性。如果序列化器找到给定类型的节点,则会直接复制它。例如:
<serializer name="KeepDate" >
<keep type="DateTime"/>
</serializer>
特殊化
对于希望以特殊方式处理的类型,应该使用 specialization
节点。以下是其外观:
<serializer name="EpocDate">
<specialization
type="DateTime"
outType="int"
serialization="dateTimeToEpoc"
deserialization="dateTimeFromEpoc"
import="package:entity_serializer/entity_serializer.dart"
/>
</serializer>
这里是对属性的描述:
type
- 这条规则适用的类型。outType
-serialization
属性所指向函数的结果。serialization
- 应该使用的函数名称,用于将type
转换为outType
。deserialization
- 应该使用的函数名称,用于将outType
转换为type
。import
- 函数可以从哪个包/源文件导入,这是一个可选属性。
类节点
每个需要生成的数据类都必须放入XML中的 class
节点内。以下属性可用于此节点:
name
- 必须的,告诉新类的名称,应该是PascalCase风格。copyWith
- 可选,默认为true
。告诉新创建的类是否应具有@CopyWith
注解。serializers
- 可选,默认为null
。应为此类生成的所有序列化器的列表。如果为空/空,则使用所有已知序列化器。generateEntity
- 可选,默认为true
。告诉构建器是否应生成Dart类。如果是false
,则仅生成序列化器。需要手动导入类体。apiProxy
- 可选,默认为空。应为此实体生成的API代理的逗号分隔列表。参见proxy
节点文档了解更多信息。mixin
- 可选,默认为空。应添加到类定义中的混合的逗号分隔列表。
要为新创建的类创建字段,可以添加子节点。每个子节点支持以下属性:
name
- 必须的,告诉字段应如何命名,适用于Dart语言的命名限制。final
- 可选,默认为true
。如果设置为true
,则使类字段为final
。comment
- 可选,默认为null
。如果设置,可以包括字段的注释。
示例:
<?xml version="1.0"?>
<spec>
<class name="Book">
<string name="name"/>
<string name="author" final="false"/>
</class>
</spec>
结果为:
@CopyWith()
class Book {
final String name;
String author;
MyType({
required this.name,
required this.author,
});
}
类子节点:普通Dart类型
以下是为普通Dart类型保留的子节点:int
、double
、bool
、String
。
示例:
<?xml version="1.0"?>
<spec>
<class name="Book">
<string name="str1"/>
<optionalString name="str2"/>
<int name="int1"/>
<optionalInt name="int2"/>
<double name="dbl1"/>
<optionalDouble name="dbl2"/>
<bool name="bool1"/>
<optionalBool name="bool2"/>
</class>
</spec>
类子节点:集合 - 列表
对于Dart List
类型,需要使用专门的节点,它具有上述所有属性外加必需的 innerType
属性。示例:
<class name="MyList">
<list name="integers" innerType="int"/>
<list name="customType" innerType="MyType"/>
<list name="dynamicType" innerType="dynamic"/>
<list name="expDynamicType" innerType="dynamic" expectOnly="MyType,MyOther"/>
</class>
值得注意的是,innerType
可以指向 dynamic
,这将生成额外代码以处理此XML中所有数据类型的类。如果要限制动态列表中的类检测,则 expectOnly
属性发挥作用。你可以指定仅处理那些类型。
类子节点:集合 - 映射
对于Dart Map
类型,需要使用专门的节点,它具有上述所有属性外加两个必需的 keyType
和 valueType
属性。valueType
的行为与 list
节点中的 innerType
相同。简单示例:
<class name="MyMap">
<map name="integers" keyType="String" valueType="int"/>
<map name="strings" keyType="String" valueType="String"/>
<map name="customType" keyType="String" valueType="MyType"/>
<map name="dynamicType" keyType="String" valueType="dynamic"/>
<map name="expDynamicType" keyType="String" valueType="dynamic" expectOnly="MyType,MyOther"/>
</class>
类子节点:字段
class
节点中最通用的子节点是 field
,它允许你在类中添加任意类型的变量。它有两个必需的属性 name
和 type
。示例:
<class name="Book" copyWith="false">
<field name="author" type="Author"/>
<field name="meta" type="Meta?"/>
<optionalField name="extra" type="String"/>
</class>
类子节点:任何名称
如果你想在其他类中使用自定义数据类作为变量类型,只需输入其名称。请看这个小示例:
<spec>
<class name="Book" copyWith="false">
<field name="author" type="Author"/>
<field name="meta" type="Meta"/>
<Meta name="anotherMeta"/>
<meta name="andAnotherMeta"/>
</class>
<class name="Author" copyWith="false">
<string name="name"/>
<string name="surname"/>
</class>
<class name="Meta" copyWith="false">
<String name="country"/>
</class>
</spec>
代理节点
当需要满足其他包(如 retrofit
)的要求时,代理进入游戏。这一特性允许生成其他包期望的方法。目前支持以下第三方库:
json_serializable
要启用API代理,需在文件中添加以下XML节点:
<proxy name="myProxy" type="json_serializable" serializer="MyJson"/>
其中:
name
- 必须的,你的代理名称。该名称需要在class
节点的apiProxy
属性中给出。type
- 必须的,此包支持的预定义类型。serializer
- 必须的,应包装在代理生成方法中的序列化器名称。
添加 proxy
节点后,可以向任何类节点添加 apiProxy
属性,示例:
<spec>
<serializer name="MyJson">
<specialization
type="DateTime"
serialization="dateTimeToIsoStr"
deserialization="dateTimeFromIsoStr"
import="package:entity_serializer/entity_serializer.dart"
/>
</serializer>
<proxy name="myProxy" type="json_serializable" serializer="MyJson"/>
<class name="TestEntity" apiProxy="myProxy">
<DateTime name="date"/>
<int name="justInt"/>
</class>
</spec>
结果将生成 TestEntity
类的方法 factory TestEntity.fromJson(Map<String, dynamic> json)
和 Map<String, dynamic> toJson()
。
示例代码
import 'dart:convert';
import 'package:example/src/model/pure_dynamic_test.dart';
import 'src/model/data_composition_test.dart';
import 'src/model/date_test.dart';
import 'src/model/list_test.dart';
import 'src/model/map_test.dart';
void checkListGen() {
print("####### checkListGen\n");
MyList myList = MyList(integers: [
1,
2,
3
], strings: [
"a",
"b"
], customType: [
MyTypeInList(val: 6),
MyTypeInList(val: 3)
], dynamicType: [
1,
2.4,
"a",
MyTypeInList(val: 6),
MyOtherInList(str: "str")
], expDynamicType: [
MyTypeInList(val: 36),
MyOtherInList(str: "str33")
]);
JsonEncoder encoder = JsonEncoder.withIndent(' ');
String prettyprint = encoder.convert(myList.toSer1());
print(prettyprint);
Map<String, dynamic> dec = jsonDecode(prettyprint);
final result = dec.toMyListUsingSer1();
print(result);
print("\n\n");
}
void checkCompositeGen() {
print("####### checkCompositeGen\n");
Book book = Book(
author: Author(name: "name", surname: "surname"),
meta: Meta(
country: Country(code: "code", extra: "extra"),
),
pages: 10,
superMeta: SuperMeta(version: 3),
);
JsonEncoder encoder = JsonEncoder.withIndent(' ');
final maps = book.toSer1();
String prettyprint = encoder.convert(maps);
print(prettyprint);
Map<String, dynamic> dec = jsonDecode(prettyprint);
final result = dec.toBookUsingSer1();
print(result);
print("\n\n");
}
void checkMapGen() {
print("####### checkMapGen\n");
MyMap map = MyMap(
integers: {"a": 1, "b": 2, "c": 3},
strings: {"a": "1", "b": "2", "c": "3"},
customType: {"a": MyType(val: 6), "b": MyType(val: 3)},
dynamicType: {
"a": 1,
"b": 2.4,
"c": "str",
"d": MyType(val: 6),
"e": MyOther(str: "str")
},
expDynamicType: {"a": MyType(val: 36), "b": MyOther(str: "str33")},
);
JsonEncoder encoder = JsonEncoder.withIndent(' ');
final maps = map.toSer1();
String prettyprint = encoder.convert(maps);
print(prettyprint);
Map<String, dynamic> dec = jsonDecode(prettyprint);
final result = dec.toMyMapUsingSer1();
print(result);
print("\n\n");
}
void checkDateGen() {
print("####### checkDateGen\n");
TestEntity entity = TestEntity(date: DateTime(1999, 1, 2, 12, 30, 55));
JsonEncoder encoder = JsonEncoder.withIndent(' ');
final maps = entity.toIsoDate();
String prettyprint = encoder.convert(maps);
print(prettyprint);
Map<String, dynamic> dec = jsonDecode(prettyprint);
final result = dec.toTestEntityUsingIsoDate();
print(result.date);
print("\n\n");
}
void checkPureEvil() {
EvilDynamic evilDynamic = EvilDynamic(
evil: EvilTwo(justString: "s"),
limitedEvil: EvilOne(justInt: 1),
);
final maps = evilDynamic.toSer();
final dec = maps.toEvilDynamicUsingSer();
print(dec.evil.justString);
print(dec.limitedEvil.justInt);
}
void main() {
checkListGen();
checkCompositeGen();
checkMapGen();
checkDateGen();
checkPureEvil();
}
更多关于Flutter实体序列化插件entity_serializer的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter实体序列化插件entity_serializer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
entity_serializer
是一个用于 Flutter 的实体序列化插件,它可以帮助开发者将 Dart 对象(实体类)序列化为 JSON 格式,或者将 JSON 格式反序列化为 Dart 对象。这个插件的主要目的是简化实体类的序列化和反序列化过程,减少样板代码的编写。
安装 entity_serializer
首先,你需要在 pubspec.yaml
文件中添加 entity_serializer
依赖:
dependencies:
flutter:
sdk: flutter
entity_serializer: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
来安装依赖。
使用 entity_serializer
1. 定义实体类
假设我们有一个 User
实体类,包含 id
、name
和 email
三个属性:
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
2. 生成序列化代码
entity_serializer
提供了一个注解 [@Entity](/user/Entity)
,你可以使用这个注解来标记需要序列化的实体类。然后,entity_serializer
会自动生成序列化和反序列化的代码。
import 'package:entity_serializer/entity_serializer.dart';
[@Entity](/user/Entity)()
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
在终端中运行以下命令来生成序列化代码:
flutter packages pub run build_runner build
这将会生成一个 user.g.dart
文件,其中包含 User
类的序列化和反序列化代码。
3. 使用生成的代码
生成的代码中会包含 toJson
和 fromJson
方法,你可以使用这些方法来序列化和反序列化 User
对象。
import 'user.g.dart';
void main() {
// 创建一个 User 对象
User user = User(id: 1, name: "John Doe", email: "john.doe@example.com");
// 序列化为 JSON
Map<String, dynamic> json = user.toJson();
print(json); // 输出: {id: 1, name: John Doe, email: john.doe@example.com}
// 反序列化为 User 对象
User userFromJson = User.fromJson(json);
print(userFromJson.name); // 输出: John Doe
}
自定义序列化行为
如果你需要自定义某些字段的序列化行为,可以在字段上使用 [@SerializedName](/user/SerializedName)
注解来指定 JSON 中的字段名,或者使用 [@SerializableField](/user/SerializableField)
注解来指定自定义的序列化和反序列化逻辑。
[@Entity](/user/Entity)()
class User {
[@SerializedName](/user/SerializedName)("user_id")
final int id;
final String name;
[@SerializableField](/user/SerializableField)(
toJson: (email) => email.toString().toLowerCase(),
fromJson: (json) => json.toString(),
)
final String email;
User({required this.id, required this.name, required this.email});
}