如果你要与一个API进行交互,你认为响应会是什么格式?它可能--或者几乎肯定--是JSON格式。
JSON是将键入的数据从一个点传输到另一个点的标准。如果你查询API,或者甚至想在你的应用程序中保存设置,你迟早都要使用JSON。
JSON中的序列化和反序列化
在处理JSON时,有两个主要动作可以发生:
- 序列化 - 将一个数据对象从其母语类型转换为JSON字符串
- 反序列化--从JSON字符串转换为本地对象
实际上,这是我们现在就可以做的事情,就在你用来阅读这篇文章的那个浏览器中。如果你按下键盘上的F12键,进入开发者控制台,你可以输入以下内容:
JSON.stringify({serialization: 'fun'})
这样做的结果是:'{"serialization":"fun"}'
好的,那么刚才发生了什么?我们创建了一个内存中的JavaScript对象,然后立即通过JSON.stringify 将该对象转换为JSON。我们可以看到它是一个字符串,因为它被引号所包围。
我们可以通过运行以下程序来反向运行这个过程:
JSON.parse('{"serialization":"fun"}')
这样做的结果如下:

现在,我们要把这个对象的字符串表示转换回真正的JavaScript对象。由于JavaScript是一种非类型语言,我们不需要指定我们想要序列化或反序列化的JSON字符串的类型。
在一个类型化的语言中,比如C#,从对象到字符串的序列化和反序列化是类似的。然而,有一点不同的是,当我们反序列化时,我们需要通过以下方式指定我们希望反序列化的类型:
JsonConvert.DeserializeObject<T>
在上面的命令中,T 是我们试图反序列化的目标类型。
也许你会认为在Flutter中序列化数据这样简单而普遍的事情会像在C#或JavaScript中完成同样的事情一样容易。不幸的是,情况并非如此,但这种挑战是有原因的。
为什么在Flutter中解析JSON是一个挑战?
在Flutter中序列化和反序列化JSON字符串与其他语言的关键区别在于Flutter不支持被称为 "反射 "的运行时功能。通过反射,其他语言能够检查对象的信息,帮助对象序列化。
然而,Flutter缺乏反射实际上是一件好事。没有反射,Flutter代码避免了相关的性能注意事项,并为终端用户带来了更小的可安装二进制文件。
我们仍然可以在Flutter中轻松解析JSON字符串,但我们需要做的不仅仅是指定一个类型。幸运的是,我们可以生成完成这一任务所需的所有代码,因此我们不必手工编写。
当你想在Flutter中序列化数据时,有两种情况可以发生:
一种情况是,你自己控制着数据类,你想把你的数据转换为JSON,或从JSON中转换出来,发送到API或存储在设备上。
在另一种情况下,你自己不控制数据类,而从API接收JSON。你想把这个JSON转换成强类型的数据,你可以在你的Flutter应用程序中操作。
在这篇文章中,我们将考虑这两种情况。首先,我们将看看当我们控制数据时,如何在应用中序列化一个类。然后,我们将回顾在Flutter中从远程来源反序列化JSON数据。
在 Flutter 应用程序中用 JSON 序列化一个类
让我们想象一下,我们有一个音乐应用程序,用户可以在其中设置歌曲作为他们的最爱。为了实现这一目的,我们需要一个名为Favorite 的类。在这个类中,我们通常会有一些属性,如歌曲的标题和艺术家。
最终的结果会是这样的:
class Favorite {
final String title;
final String artist;
Favorite(this.title, this.artist);
}
那么,我们如何在Flutter中对这样的类进行序列化和反序列化?你可以自己做,或者自动生成JSON处理代码。让我们来看看这两种选择。
定义您自己的toJson 和fromJson 方法
如果你只有几个想序列化或反序列化的类,可以选择简单地自己做。这意味着定义你自己的toJson 和fromJson 方法,这些方法负责将数据从Map<String, dynamic> 转换回原始数据模型。
如果我们这样做,我们的类就会是这样的:
class Favorite {
final String title;
final String artist;
Favorite(this.title, this.artist);
/// Convert from JSON response stream to Favorite object
Favorite.fromJson(Map<String, dynamic> json)
: title = json['title'],
artist = json['artist'];
/// Convert an in-memory representation of a Favorite object to a Map<String, dynamic>
Map<String, dynamic> toJson() => {
'title': title,
'artist': artist,
};
}
然后我们可以用jsonEncode 或jsonDecode ,分别使用我们的fromJson 和toJson 方法。但在Flutter中定义自己的JSON数据转换方法有一些缺点。
例如,如果我们曾经更新了类的成员,我们将不得不手动更新属性。我们也有可能参考了错误的成员,或者只是打错了字,试图意外地映射一个不存在的属性。
这种方法也为我们在Flutter应用程序中处理JSON增加了很多开销。使用现有的包来生成JSON序列化和反序列化代码会更方便。
自动生成您的JSON处理代码
官方Flutter文档说,对于较小的应用程序,您可以手工编写自己的JSON映射代码。在现实中,你总是更好地生成JSON序列化和反序列化代码。
自动生成JSON处理代码的包都是经过严格测试的,并且像Flutter一样已经存在了很长时间,所以你知道它们是有效的。
这些包中的一个是 [json_serializable](https://pub.dev/packages/json_serializable).在我们的Flutter代码中使用一些简单的属性,我们可以在短时间内为JSON序列化生成我们的代码。
首先,将json_serializable 添加到你的pubspec.yaml 文件中,作为dependency ,也作为dev_dependency 。我们还需要添加对build_runner 的引用,因为json_serializable 使用build_runner 来为我们创建功能。
接下来,用@JsonSerializable 来注释我们的类。这将指示json_serializable 包生成我们需要的代码,以便在Flutter中把这个类序列化成JSON。
我们还想在这个文件的顶部添加part 指令,并指定favorite.g.dart 。当我们最初写这个的时候,我们会在下面看到红色的方格,因为这个文件还没有被创建。但是我们需要在我们的文件中定义这个,以使json_serializable 包能够工作。
part 'favorite.g.dart'
@JsonSerializable
class Favorite {
final String title;
final String artist;
Favorite(this.title, this.artist);
}
有了这些变化,现在我们可以在终端运行以下命令:
flutter pub run build_runner build
在一些控制台输出后,我们就能看到我们的favorites.g.dart 文件出现在我们的项目中。该文件的内容如下:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'favorite.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Favorite _$FavoriteFromJson(Map<String, dynamic> json) => Favorite(
json['title'] as String,
json['artist'] as String,
);
Map<String, dynamic> _$FavoriteToJson(Favorite instance) => <String, dynamic>{
'title': instance.title,
'artist': instance.artist,
};
不幸的是,json_serializable 并没有在生成的代码中直接暴露这些toJson 或fromJson 的功能。这意味着我们要把它们包含在我们的原始文件中。总而言之,我们最终的favorite.dart 文件应该是这样的:
import 'package:json_annotation/json_annotation.dart';
part 'favorite.g.dart';
@JsonSerializable()
class Favorite {
final String title;
final String artist;
Favorite(this.title, this.artist);
factory Favorite.fromJson(Map<String, dynamic> json) => _$FavoriteFromJson(json);
Map<String, dynamic> toJson() => _$FavoriteToJson(this);
}
当我们想对Favorite 对象进行序列化或编码时,我们使用以下命令:
final favoriteJson = jsonEncode(Favorite('one', 'two'));
如果我们想把这个基于文本的JSON转换回原始对象,我们可以做以下工作:
final favorite = Favorite.fromJson(jsonDecode(favoriteJson));
这就是我们在自己的应用程序中控制对象时管理JSON的方法。但当我们不控制数据类时,我们该怎么做呢?例如,如果我们正在查询一个我们没有数据结构的远程API?
在 Flutter 中从远程来源反序列化 JSON 数据
如果我们有一个远程API,我们可以使用一个在线工具,将提供的JSON转换为我们可以在我们的应用程序中使用的类,其中包括toJson 和fromJSON 方法。我们要用来演示的一个工具是Github上的一个JSON到Dart的转换器。
在这个例子中,我们将使用Bored API,它可以在我们感到无聊的时候随机给我们一个任务做。Bored API以JSON格式响应,很适合这个目的。由于它是随机的,你收到的任务将与我们下面使用的例子不同。
在我的例子中,我从API收到的响应是如下:
{"activity":"Go for a walk","type":"relaxation","participants":1,"price":0,"link":"","key":"4286250","accessibility":0.1}
要在我们的Flutter应用程序中序列化和解析这些JSON数据,只需将其粘贴到JSON到Dart转换器中,如下图所示:

现在,我们只需将BoredAPI 类复制并粘贴到我们的应用程序中就可以了。在这种情况下,我们为Bored API生成的代码会是这样的:
class BoredAPI {
String? activity;
String? type;
int? participants;
int? price;
String? link;
String? key;
double? accessibility;
BoredAPI(
{this.activity,
this.type,
this.participants,
this.price,
this.link,
this.key,
this.accessibility});
BoredAPI.fromJson(Map<String, dynamic> json) {
activity = json['activity'];
type = json['type'];
participants = json['participants'];
price = json['price'];
link = json['link'];
key = json['key'];
accessibility = json['accessibility'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['activity'] = this.activity;
data['type'] = this.type;
data['participants'] = this.participants;
data['price'] = this.price;
data['link'] = this.link;
data['key'] = this.key;
data['accessibility'] = this.accessibility;
return data;
}
}
我们可以看到,我们有所有的类成员。我们也声明了fromJson 和toJson 函数。另外,我们生成的代码支持空安全,所以我们可以在更多的Flutter版本中使用它。
有了这些,我们现在可以安全地与这个API交互,并根据需要在Flutter中对JSON数据进行编码和解码。
总结
在Flutter中解析JSON字符串比在其他语言中要困难一些。正如我们所看到的,这是由于Flutter内缺乏运行时的反射。
然而,在我看来,Flutter通过让我们根据需要生成适用的类来序列化我们的JSON,从而弥补了这些小的烦恼。
我们还看到,即使我们不拥有进入我们应用程序的数据,我们也可以通过为从API收到的响应生成JSON类来创建转换为JSON的功能。
如果你正在与API进行交互,并且对JSON进行序列化,你也可以考虑使用像OpenAPI或Swagger这样的东西来为你生成所有的API代码。这样,你就可以只处理API功能,而不是手动将你的对象转换为JSON。