手动 JSON 解码是指在?dart:convert
?中使用内置的 JSON 解码器。它包括将原始 JSON 字符串传递给?jsonDecode()
?方法,然后在产生的?Map<String, dynamic>
?计算结果中寻找你需要的值。它没有外部依赖或者特定的设置过程,这有利于快速证明概念。
下面的样例实现了一个简单用户模型。
{
"name": "John Smith",
"email": "john@example.com"
}
通过?dart:convert
,你可以调用?jsonDecode()
?方法来解码 JSON。
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_serializable?和?built_value?就是这类的库。
这种方法适用于大型项目。不需要手动编写模板,并且一些试图去获取不存在的 JSON 字段的笔误,会在编译阶段被发现。代码生成的麻烦之处,在于它需要一些初始化设置。并且,生成的源文件可能在你的项目导航中产生一些视觉上的混乱。
当你有一个中大型项目时,你可能想要使用生成的代码来进行 JSON 序列化。要看基于代码生成的 JSON 编码,见?使用代码生成库序列化 JSON 数据。
本指南使用了?json_serializable,一个自动化源代码生成器来为你生成 JSON 序列化数据模板。结合build_runner。
下面显示了怎样将?User
?类转换为?json_serializable
?后的类。简单起见,该代码使用了前面的例子中的简化的 JSON 模型。
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 编码及解码?name
?以及?email
?字段的代码。
如果需要,你可以很轻易地自定义命名策略。例如,如果 API 返回带有?蛇形命名方式?的对象,并且你想要在你的模型里使用?小驼峰?的命名方式,你可以使用带有一个 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.snake)
?与添加?@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;
当你首次创建?json_serializable
?类时,你会得到类似下图的错误。
这些错误完全正常,很简单,因为这些模型类的生成代码并不存在。要解决这个问题,你需要运行代码生成器来生成序列化数据模板。
有两种方式运行代码生成器。
通过在项目根目录运行命令?flutter pub run build_runner build --delete-conflicting-outputs
,你可以在任何需要的时候为你的模型生成 JSON 序列化数据代码。这会触发一次构建,遍历源文件,选择相关的文件,然后为它们生成必须的序列化数据代码。
虽然这样很方便,但是如果你不需要在每次修改了你的模型类后都要手动构建那将会很棒。
监听器?让我们的源代码生成过程更加方便。它会监听我们项目中的文件变化,并且会在需要的时候自动构建必要的文件。你可以在项目根目录运行?flutter pub run build_runner watch
?启动监听。
启动监听并让它留在后台运行是安全的。
为了以?json_serializable
?的方式解码 JSON 字符串,你不必对以前的代码做任何的改动。
Map<String, dynamic> userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
编码也是如此。调用 API 和以前一样。
String json = jsonEncode(user);
在使用了?json_serializable
?后,你可以立马忘掉?User
?类中所有手动序列化的 JSON 数据。源代码生成器会创建一个名为?user.g.dart
?的文件,它包含了所有必须的序列化数据逻辑。你不必再编写自动化测试来确保序列化数据奏效。现在?由库来负责?确保序列化数据能正确地被转换。
你可能类在代码中用了嵌套类,在你把类作为参数传递给一些服务(比如 Firebase)的时候,你可能会遇到?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 '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()
?看起来会像下面这样:
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'name': instance.name,
'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 '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?参数等相关文档。