Flutter入门-Json序列化与反序列化

516 阅读6分钟

encode/序列化即将数据结构序列化为字符串,decode/反序列化即为将字符串解码成数据结构。在

简单结构(没有数组)

Flutter中序列化与反序列化主要有两种方式

  • 手动
  • 自动

手动

Flutter有一个内置的dart:convert库,其中包含一个简单的JSON编码器和解码器

Map<String, dynamic> user = jsonDecode(jsonString);

print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');

String json = jsonEncode(user);

jsonDecode()方法会返回一个类型为Map<String, dynamic>的类型,解码(Decode)相反的是编码(Encode),如果我们想要对User进行编码,我们可以使用jsonEncode()方法

// user.dart
class User {
  final String name;
  final String email;

  User(this.name, this.email);

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        email = json['email'];

  Map<String, dynamic> toJson() => {
        'name': name,
        'email': email,
      };
}

此外,我们可以引入一个简单的模型类(在本例中称为User)来解决前面提到的问题。在User类中,我们可以发现:

  • User.fromJson()构造函数,用于从Map构造新的User实例。
  • toJson()方法,将User实例转换为Map

自动

尽管有其他库可用,但是这里使用了json_serializable,这是一个自动源代码生成器,可为我们生成json序列化模版。

要在项目中包含json_serializable,需要一个常规依赖项和两个开发依赖项。简而言之,开发依赖项是不包含在我们的应用程序源代码中的依赖项,它们只在开发环境中使用 我们需要在pubspec.yaml进行如下配置:

**pubspec.yaml**
dependencies:
  # Your other regular dependencies here
  json_annotation: <latest_version>

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: <latest_version>
  json_serializable: <latest_version>

然后在项目根文件夹中运行flutter pub-get以安装依赖。

然后我们以json_serializable的方式创建模型类:

// user.dart
import 'package:json_annotation/json_annotation.dart';

/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';

/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()
class User {
  User(this.name, this.email);

  String name;
  String email;

  /// A necessary factory constructor for creating a new User instance
  /// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
  /// The constructor is named after the source class, in this case, User.
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  /// `toJson` is the convention for a class to declare support for serialization
  /// to JSON. The implementation simply calls the private, generated
  /// helper method `_$UserToJson`.
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

通过这种设置,源代码生成器生成用于对JSON中的nameemail字段进行编码和解码的代码。

如果需要的话,我们还可以定制命名策略,比如,如果API返回带有的对象带有snake_case属性,并且我们希望在模型中使用lowerCamelCase,则可以使用带有name参数的@JsonKey注释:

/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;

服务器和客户端最好都遵循相同的命名策略。

@JsonSerializable()提供了fieldRename的枚举,用于将dart字段完全转换为JSON键。

修改@JsonSerializable(fieldRename:fieldRename.sake)相当于向每个字段添加@JsonKey(name:“<snake_case>”)

服务器返回的数据是不确定的,所以有必要验证和保护客户端上的数据。

其他常用的@JsonKey注释包括:

/// Tell json_serializable to use "defaultValue" if the JSON doesn't
/// contain this key or if the value is `null`.
@JsonKey(defaultValue: false)
final bool isAdult;

/// When `true` tell json_serializable that JSON must contain the key, 
/// If the key doesn't exist, an exception is thrown.
@JsonKey(required: true)
final String id;

/// When `true` tell json_serializable that generated code should 
/// ignore this field completely. 
@JsonKey(ignore: true)
final String verificationCode;

生成方式

一次性代码生成

通过在项目根目录中运行

flutter pub run build_runner build --delete-conflicting-outputs

我们可以在需要时为模型生成JSON序列化代码。这将触发一次性构建,该构建将遍历源文件,选择相关文件,并为它们生成必要的序列化代码。

虽然这很方便,但如果我们不必每次在模型类中进行更改时都手动运行构建,那就更好了。

持续生成代码

观察者模式使我们的源代码生成过程更加方便。它监听项目文件中的更改,并在需要时自动生成必要的文件。 通过在项目根目录中运行

flutter pub run build_runner watch --delete-conflicting-outputs

可以安全地启动一次观察程序,并让它在一直后台运行。

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂结构

所谓复杂结构其实就是包含数组结构的数据,因为普通键值对结构的数据原生的序列化与反序列化都已经能够做到 如对以下数据

{
  "code":0,
  "message":"Success",
  "data":[
    {
      "name":"Jerry",
      "email":"Jerry@example.com"
    },
    {
      "name":"Alex",
      "email":"Alex@example.com"
    },
    {
      "name":"Tom",
      "email":"Tom@example.com"
    },
    {
      "name":"Jack",
      "email":"Jack@example.com"
    },
    {
      "name":"Lucy",
      "email":"Lucy@example.com"
    }
  ]
}

嵌套Entity

// To parse this JSON data, do
//
//     final userList = userListFromJson(jsonString);

import 'dart:convert';
import 'user.dart';

UserList userListFromJson(String str) => UserList.fromJson(json.decode(str));

String userListToJson(UserList data) => json.encode(data.toJson());

class UserList {
  int code;
  String message;
  List<User> data;

  UserList({
    required this.code,
    required this.message,
    required this.data,
  });

  factory UserList.fromJson(Map<String, dynamic> json) => UserList(
        code: json["code"],
        message: json["message"],
        data: List<User>.from(json["data"].map((x) => User.fromJson(x))),
      );

  Map<String, dynamic> toJson() => {
        "code": code,
        "message": message,
        "data": List<dynamic>.from(data.map((x) => x.toJson())),
      };
}

/// YApi QuickType插件生成,具体参考文档:https://plugins.jetbrains.com/plugin/18847-yapi-quicktype/documentation

import 'dart:convert';

User userFromJson(String str) => User.fromJson(json.decode(str));

String userToJson(User data) => json.encode(data.toJson());

class User {
    User({
        required this.name,
        required this.email,
    });

    String name;
    String email;

    factory User.fromJson(Map<String, dynamic> json) => User(
        name: json["name"],
        email: json["email"],
    );

    Map<String, dynamic> toJson() => {
        "name": name,
        "email": email,
    };
}

泛型写法

使用 json_serializable 结合泛型,我们可以先写出数据返回的基类,这和Android原生开发的思路是一样的,对于后面结合网络请求,也是使用这种方式

import 'package:json_annotation/json_annotation.dart';

part 'base_response.g.dart';

/// Time: 2023/9/4/0004 on 16:49
/// User: Jerry
/// Description: 根据服务器接口返回格式统一标准BaseResponse
/// 1、一次性代码生成
/// 通过在项目根目录运行命令 flutter pub run build_runner build --delete-conflicting-outputs,
/// 你可以在任何需要的时候为你的模型生成 JSON 序列化数据代码。这会触发一次构建,遍历源文件,选择相关的文件,
/// 然后为它们生成必须的序列化数据代码。 虽然这样很方便,但是如果你不需要在每次修改了你的模型类后都要手动构建那将会很棒。
/// 2、持续生成代码
/// 监听器 让我们的源代码生成过程更加方便。它会监听我们项目中的文件变化,并且会在需要的时候自动构建必要的文件。
/// 你可以在项目根目录运行 flutter pub run build_runner watch 启动监听。
/// 启动监听并让它留在后台运行是安全的。

/// 一个注释,用于代码生成器,使其知道该类需要生成JSON序列化逻辑
@JsonSerializable(genericArgumentFactories: true)
class BaseResponse<T> {
  //消息(例如成功消息文字/错误消息文字)
  String? message;

  bool? success = false;

  //自定义code(可根据内部定义方式)
  int? code;

  //接口返回的数据
  T? data;

  BaseResponse({
    this.message,
    this.success,
    this.code,
    this.data,
  });

  /// 从映射创建新BaseResponse实例所需的工厂构造函数。将映射传递给生成的' _BaseResponseFromJson() '构造函数。
  /// 构造函数以源类命名,在本例中为BaseResponse。
  factory BaseResponse.fromJson(
          Map<String, dynamic> json, T Function(dynamic json) fromJsonT) =>
      _$BaseResponseFromJson<T>(json, fromJsonT);

  /// ' toJson '是类声明支持序列化为JSON的约定。该实现仅仅调用私有的、生成的助手方法' _BaseResponseToJson '。
  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$BaseResponseToJson<T>(this, toJsonT);
}

Future decodeAssetJson() async {
    // 解析本地数据-List数组
    Map<String, dynamic> userListMap =
        await loadJsonAssets("assets/user_list.json");

    // 列表转换时一定要加一下强转List<dynamic>,否则会报错:type 'List<dynamic>' is not a subtype of type 'List<User>'
    BaseResponse<List<User>> userListMap2 = BaseResponse.fromJson(
        userListMap,
        (json) => (json as List<dynamic>)
            // .map((e) => User.fromJson(e as Map<String, dynamic>))
            .map((e) => User.fromJson(e))
            .toList());

    var usersMap = userListMap2.data;
    
}