很难想象一个移动应用会不需要与 web 服务器通信或者在某些时候轻松地存储结构化数据。当创造需要网络连接的应用时,它可能迟早会处理一些旧的 JSON。
本指南介绍了如何在 Flutter 中使用 JSON。包括了如何在不同场景中使用相应的 JSON 解决方案以及为什么要这么做。
术语:编码 和 序列化数据 是一回事 - 将数据结构转换为字符串。解码 和 反序列化数据 则是相反的过程 - 将字符串转换为数据结构。然而,序列化数据 通常也指将数据结构转换为更加易读的数据格式的整个过程。
为了避免混淆,本文档在涉及到整个过程时使用“序列化数据”,在特指这些过程时使用“编码”和“解码”。
1. 我需要哪一种 JSON 序列化数据方法?
本文涵盖了两种常规的 JSON 使用策略:
手动序列化数据
利用代码生成进行自动序列化数据
不同的项目复杂度不同,用例也不一样。对于较小的概念验证项目或者快速原型,使用代码生成器可能是过度的。对于具有很多更加复杂的 JSON 模型的 App,手动编码可能很快变得无聊,重复并且发生很多小错误。
手动 JSON 解码是指在 dart:convert
中使用内置的 JSON 解码器。它包括将原始 JSON 字符串传递给 jsonDecode()
方法,然后在产生的 Map<String, dynamic>
计算结果中寻找你需要的值。它没有外部依赖或者特定的设置过程,这有利于快速证明概念。
当你的项目变大时,手动解码表现得并不理想。手动编写解码逻辑会变得难以管理并容易出错。如果你产生了笔误去获取一个不存在的 JSON 字段,你的代码会在运行时抛出一个错误。
如果你的项目没有很多的 JSON 模型并且你正在寻找一个快速测试概念的方法,手动序列化数据可能是你要的开始的方式。关于手动编码的示例,请参阅第三章 3. 使用 dart:convert 手动序列化 JSON 数据。
1.2 为中大型项目使用代码生成
利用代码生成的 JSON 序列化数据意味着有外部的库为你生成编码模板。在一些初始化设置后,你可以运行文件监听程序来从你的模型类生成代码。例如,json_serializable 和 built_value 就是这类的库。
这种方法适用于大型项目。不需要手动编写模板,当试图去获取不存在的 JSON 字段时的笔误会在编译阶段被发现。代码生成的缺点是它需要一些初始化设置。并且,生成的源文件可能在你的项目导航中产生一些视觉上的混乱。
当你有一个中大型项目时,你可能想要使用生成的代码来进行 JSON 序列化。要看基于代码生成的 JSON 编码,见 使用代码生成库序列化 JSON 数据。
2. Flutter 中是否有 GSON/Jackson/Moshi 的等价物
简单来说是没有。
这样的库需要使用运行时反射,这在 Flutter 中是被禁用的。运行时反射会影响被 Dart 支持了相当久的 tree shaking。通过 tree shaking,你可以从你的发布版本中“抖掉”不需要使用的代码。这会显著优化 App 的体积。
由于反射会默认让所有的代码被隐式使用,这让 tree shaking 变得困难。工具不知道哪一部分在运行时不会被用到,所以冗余的代码很难被清除。当使用反射时,App 的体积不能被轻易优化。
尽管你不能在 Flutter 中使用运行时反射,还是有一些库提供了基于代码生成的方便使用的 API。
3. 使用 dart:convert 手动序列化 JSON 数据
在 Flutter 中基本的 JSON 编码是十分容易的。Flutter 有一个内置的 dart:convert
的库,这个库包含了一个简单的 JSON 编码器和解码器。
这是一个简单的用户模型的示例 JSON。
{
"name": "John Smith",
"email": "[email protected]"
}
通过 dart:convert
,你可以用两种方法编码这个 JSON 模型。
3.1 内联序列化 JSON 数据
jsonDecode()
方法来解码 JSON。
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
jsonDecode()
返回一个
Map<String, dynamic>
,这意味着你在运行时以前都不知道值的类型。 使用这个方法,你失去了大部分的静态类型语言特性: 类型安全,自动补全以及最重要的编译时异常。 你的代码会立即变得更加容易出错。
name
或者
email
字段,你可能很快引入一个笔误。 然而编译器却无法知道 map 中有 JSON 笔误,编译器并不知道这个笔误。
User
。 在
User
类中,你会发现:
-
一个
User.fromJson()
构造函数,用于从 map 结构中构造一个新的User
实例 -
一个
toJson()
方法,这个方法会将User
实例转换为一个 map
name
和
email
字段的自动完成以及编译时异常(检测)。 如果你发生了笔误或者把
String
类型的字段看成了
int
类型,App 将不会编译,而不是在运行时崩溃。
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,
};
}
Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
User
对象传到
jsonEncode()
函数中。 你不需要调用
toJson()
方法,因为
jsonEncode()
已经帮你做了这件事。
String json = jsonEncode(user);
User.fromJson()
和
User.toJson()
方法都需要单元测试以便验证正确的行为。
json_serializable package
,一个自动化源代码生成器来为你生成 JSON 序列化数据模板。
json_serializable
,你需要一个常规依赖,以及两个 dev 依赖。 简单来说,dev 依赖 是不包括在我们的 App 源代码中的依赖 - 它们只会被用在开发环境中。
pubspec
文件 中查看。
dependencies:
# Your other regular dependencies here
json_annotation: ^2.0.0
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^1.0.0
json_serializable: ^2.0.0
flutter pub get
(或者在你的编辑器中点击 Packages Get)以确保在你的项目中可以使用这些新的依赖。
User
类转换为
json_serializable
后的类。 简单起见,该代码使用了前面的例子中的简化的 JSON 模型。
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);
}
name
以及
email
字段的代码。
@JsonKey
注解。
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
json_serializable
类时,你会得到类似下图的错误。
flutter pub run build_runner build
,你可以在任何需要的时候为你的模型生成 JSON 序列化数据代码。 这会触发一次构建,遍历源文件,选择相关的文件,然后为它们生成必须的序列化数据代码。
flutter pub run build_runner watch
启动监听。
json_serializable
的方式解码 JSON 字符串,事实上你不必对以前的代码做任何的改动。
Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
String json = jsonEncode(user);
json_serializable
,在
User
类中你可以忘记手动序列化任意的 JSON 数据。 源代码生成器会创建一个名为
user.g.dart
的文件,它包含了所有必须的序列化数据逻辑。 你不必再编写自动化测试来确保序列化数据奏效。 现在由
库来负责 确保序列化数据能正确地奏效。
Invalid argument
错误。
Address
类:
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
类中使用:
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
String firstName;
Address address;
User(this.firstName, this.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()
会看起来像下面这样:
(
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'firstName': instance.firstName,
'address': instance.address,
};
print
用户对象时:
Address address = Address("My st.", "New York");
User user = User("John", address);
print(user.toJson());
{name: John, address: Instance of 'address'}
{name: John, address: {street: My st., city: New York}}
@JsonSerializable
方法加入
explicitToJson: true
参数,
User
类现在看起来是这样的:
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable(explicitToJson: true)
class User {
String firstName;
Address address;
User(this.firstName, this.address);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}