Flutter实体序列化插件entity_serializer的使用

Flutter实体序列化插件entity_serializer的使用

entity_serializer 是一个用于简化数据类和序列化器构建过程的Flutter插件。该插件通过XML文件描述模型,生成相应的Dart代码,从而实现数据类的序列化和反序列化。

功能简介

  • Pub Package: Pub Package
  • GitHub Issues: GitHub Issues
  • GitHub Forks: GitHub Forks
  • GitHub Stars: GitHub Stars
  • GitHub License: GitHub License

解决的问题

在开发过程中,经常会遇到需要创建多个数据类(如客户端与服务器之间的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 描述

有两种方法来描述模型:

  1. 所有内容都在一个文件中,使用 spec 根节点。
  2. 序列化器和实体分布在多个XML文件中。

多个XML文件(组合)

如果想将模型拆分为多个XML文件,可以按照以下步骤操作:

  1. 定义主XML文件,指向所有组件XML文件。
  2. 定义包含序列化器定义的XML文件。
  3. 定义包含实体定义的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

1 回复

更多关于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});
}
回到顶部