[Flutter]Json和序列化数据

为较小的项目使用手动序列化数据

手动 JSON 解码是指在 dart:convert 中使用内置的 JSON 解码器。它包括将原始 JSON 字符串传递给 jsonDecode() 方法,然后在产生的 Map<String, dynamic> 计算结果中寻找你需要的值。它没有外部依赖或者特定的设置过程,这有利于快速证明概念。

下面的样例实现了一个简单用户模型。

Dart 复制代码
{
  "name": "John Smith",
  "email": "john@example.com"
}

通过 dart:convert,你可以调用 jsonDecode() 方法来解码 JSON。

Dart 复制代码
Map<String, dynamic> user = jsonDecode(jsonString);

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

当你的项目变大时,手动解码表现得并不理想。手动编写解码逻辑会变得难以管理并容易出错。如果你产生了笔误去获取一个不存在的 JSON 字段,你的代码会在运行时抛出一个错误。

如果你的项目没有很多的 JSON 模型并且你正在寻找一个快速测试概念的方法,手动序列化数据可能是你要的开始的方式。关于手动编码的示例,请参阅 使用 dart:convert 手动序列化 JSON 数据

为中大型项目使用代码生成

利用代码生成的 JSON 序列化数据,意味着可以通过外部的库生成编码模板。在一些初始化设置后,你可以运行文件监听程序,来从你的模型类生成代码。例如,json_serializablebuilt_value 就是这类的库。

这种方法适用于大型项目。不需要手动编写模板,并且一些试图去获取不存在的 JSON 字段的笔误,会在编译阶段被发现。代码生成的麻烦之处,在于它需要一些初始化设置。并且,生成的源文件可能在你的项目导航中产生一些视觉上的混乱。

当你有一个中大型项目时,你可能想要使用生成的代码来进行 JSON 序列化。要看基于代码生成的 JSON 编码,见 使用代码生成库序列化 JSON 数据

本指南使用了 json_serializable,一个自动化源代码生成器来为你生成 JSON 序列化数据模板。结合build_runner。

以 json_serializable 的方式创建模型类

下面显示了怎样将 User 类转换为 json_serializable 后的类。简单起见,该代码使用了前面的例子中的简化的 JSON 模型。

user.dart

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 编码及解码 name 以及 email 字段的代码。

如果需要,你可以很轻易地自定义命名策略。例如,如果 API 返回带有 蛇形命名方式 的对象,并且你想要在你的模型里使用 小驼峰 的命名方式,你可以使用带有一个 name 参数的 @JsonKey 注解。

Dart 复制代码
/// 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.snake) 与添加 @JsonKey(name: '<snake_case>') 到每一个字段是同样的效果。

服务端的数据有时无法确认,所以在客户端很有必要进行数据校验和保护。其他常见的 @JsonKey 声明方法包括:

Dart 复制代码
/// 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;

运行代码生成工具

当你首次创建 json_serializable 类时,你会得到类似下图的错误。

这些错误完全正常,很简单,因为这些模型类的生成代码并不存在。要解决这个问题,你需要运行代码生成器来生成序列化数据模板。

有两种方式运行代码生成器。

一次性代码生成

通过在项目根目录运行命令 flutter pub run build_runner build --delete-conflicting-outputs,你可以在任何需要的时候为你的模型生成 JSON 序列化数据代码。这会触发一次构建,遍历源文件,选择相关的文件,然后为它们生成必须的序列化数据代码。

虽然这样很方便,但是如果你不需要在每次修改了你的模型类后都要手动构建那将会很棒。

持续生成代码

监听器 让我们的源代码生成过程更加方便。它会监听我们项目中的文件变化,并且会在需要的时候自动构建必要的文件。你可以在项目根目录运行 flutter pub run build_runner watch 启动监听。

启动监听并让它留在后台运行是安全的。

使用 json_serializable 模型

为了以 json_serializable 的方式解码 JSON 字符串,你不必对以前的代码做任何的改动。

Dart 复制代码
Map<String, dynamic> userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);

编码也是如此。调用 API 和以前一样。

Dart 复制代码
String json = jsonEncode(user);

在使用了 json_serializable 后,你可以立马忘掉 User 类中所有手动序列化的 JSON 数据。源代码生成器会创建一个名为 user.g.dart 的文件,它包含了所有必须的序列化数据逻辑。你不必再编写自动化测试来确保序列化数据奏效。现在 由库来负责 确保序列化数据能正确地被转换。

为嵌套类 (Nested Classes) 生成代码

你可能类在代码中用了嵌套类,在你把类作为参数传递给一些服务(比如 Firebase)的时候,你可能会遇到 Invalid argument 错误。

比如下面的这个 Address 类:

Dart 复制代码
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';

@JsonSerializable()
class Address {
  String street;
  String city;

  Address(this.street, this.city);

  factory Address.fromJson(Map<String, dynamic> json) =>
      _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}

一个 Address 类被嵌套在 User 类中使用:

Dart 复制代码
import 'package:json_annotation/json_annotation.dart';

import 'address.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  User(this.name, this.address);

  String name;
  Address address;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

在终端中运行 flutter pub run build_runner build 创建 *.g.dart文件,但私有函数 _$UserToJson() 看起来会像下面这样:

Dart 复制代码
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
  'name': instance.name,
  'address': instance.address,
};

现在看起来并没有什么问题,但当你想要打印 (print()) 这个用户对象时:

Dart 复制代码
Address address = Address('My st.', 'New York');
User user = User('John', address);
print(user.toJson());

结果会是:

Dart 复制代码
{name: John, address: Instance of 'address'}

而你期望的输出结果是这样的:

Dart 复制代码
{name: John, address: {street: My st., city: New York}}

为了得到正常的输出,你需要在类声明之前在 @JsonSerializable 方法加入 explicitToJson: true 参数, User 类现在看起来是这样的:

Dart 复制代码
import 'package:json_annotation/json_annotation.dart';

import 'address.dart';

part 'user.g.dart';

@JsonSerializable(explicitToJson: true)
class User {
  User(this.name, this.address);

  String name;
  Address address;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

了解更多信息,请查阅 json_annotation 这个 package 里的 JsonSerializable 类的 explicitToJson 参数等相关文档。

相关推荐
叽哥7 分钟前
Flutter Riverpod上手指南
android·flutter·ios
BG15 小时前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng19 小时前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭20 小时前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯20 小时前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan1 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓1 天前
Flutter Getx 的页面传参
flutter
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间2 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios