Json解析是平时日常开发的一个非常重要的部分,大部分从接口返回的数据都是Json格式,客户端通过解析Json数据来进行UI界面的绘制和展示。
Flutter给开发者提供了一个非常方便的解析库—— dart:convert来帮助开发者进行Json解析的相关操作。
下面,通过梳理平时开发中常用的一些Json数据格式,来一起看下如何使用dart:convert库来进行Json解析。
dart:convert
首先,来了解下dart:convert库的基本使用,首先需要引入这个库,代码如下所示。
import 'dart:convert' show json;
import中使用show关键字表示这里只引入一部分库代码,即引入json相关的代码。接下来,直接通过decode函数,传入json_data数据即可解析,解析返回的数据为Map,key为String类型,value为dynamic类型,类似于Java中的Object类型,这点很好理解,因为JsonObject的Value可以为多种类型,例如String、int等等,所以这里只会返回dynamic类型。
Map<String, dynamic> decodeJson = json.decode(json_data);
有了返回的Map之后,就可以直接解析Map来获得需要的数据了,这里通过一个Text组件简单的展示返回的数据,整个代码如下所示。
import 'dart:convert' show json;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Json'),
),
body: Column(
children: <Widget>[
Container(
margin: EdgeInsets.all(16),
child: FutureBuilder(
future: DefaultAssetBundle.of(context)
.loadString("assets/basicMap.json"),
builder: (context, snapshot) {
if (snapshot.hasData) {
Map<String, dynamic> decodeJso
= json.decode(snapshot.data);
return Text(decodeJson.toString());
} else {
return Text('loading...');
}
},
),
)
],
),
);
}
}
Json数据文件这里放置在Asset中,所以需要通过一个FutureBuilder来做异步加载,DefaultAssetBundle是一个帮助开发者从Asset中读取文件的工具,通过上面这个框架代码,就可以解析Json并展示数据了。
下面是笔者梳理的几种常见的Json数据格式,与在Android中操作Json类似,通常情况下,都会生成一个Json对应的Model来实现对Json的映射,不过在Flutter中,由于不能使用反射,所以不能像Android的Gson那样直接通过Model反射来实现Json-model的序列化。
JsonObject格式
这个应该是最简单的Json数据格式了,最外层是JsonObject,包含kv类型的Json数据,一个常见的示例如下所示。
{
"code": 0,
"result": "success",
"message": "message ok"
}
从这个最简单的例子开始,让我们一步步来了解Dart中的Json解析。
首先,与在Android中解析Json一样,创建一个Dart Model来进行映射,代码如下所示。
class BasicMap {
int code;
String result;
String message;
BasicMap.fromJson(Map<String, dynamic> json)
: code = json['code'],
result = json['result'],
message = json['message'];
@override
String toString() {
return 'BasicModel: code: $code ,result: $result ,message: $message';
}
}
这个类与在Android中生成的Model非常类似,首先是属性名,其次是构造函数,最后重写了toString来进行展示。这里的重点就在中间的构造函数中,该具名构造函数接受一个Map<String, dynamic>类型的参数,也就是前面提到的通过dart:convert转换出来的数据,并通过构造函数给属性赋值,值就是参数中取出的数据。修改下上面的框架代码,传入json String,就可以直接返回一个Model了,代码如下所示。
Map<String, dynamic> decodeJson = json.decode(snapshot.data);
BasicMap basicModel = BasicMap.fromJson(decodeJson);
到此为止,Dart中的Json解析和Android中的Json解析基本都是一致的。
JsonObject格式_带有数组格式数据
第一种格式中,都是基本数据类型,下面再增加一个数组类型的数据,如下所示,key:data的value是一个String数组。
{
"code": 0,
"result": "success",
"message": "message ok",
"data": [
"xuyisheng",
"zhujia"
]
}
仿照第一个的示例,来生成一个Model,代码如下所示。
class BasicMapWithList {
int code;
String result;
String message;
List<String> data;
BasicMapWithList.fromJson(Map<String, dynamic> json)
: code = json['code'],
result = json['result'],
message = json['message'],
data = json['data'];
@override
String toString() {
return 'BasicModel: code: $code ,result: $result
message: $message ,data: ${data.toString()}';
}
}
但运行之后,大家可以发现Json并不能正确的解析,并提示了下面的错误。
type 'List<dynamic>' is not a subtype of type 'List<String>'
问题就出在对String数组的解析上,data属性的类型是List,但dart:convert解析后返回的是List,不同类型的数组之间是不能相互转换的,所以代码报错了。知道了错误原因后再要解决就非常简单了,只需要指定下返回数据的类型即可,修改下构造函数,代码如下所示。
BasicMapWithList.fromJson(Map<String, dynamic> json)
: code = json['code'],
result = json['result'],
message = json['message'],
data = List<String>.from(json['data']);
通过List.from()函数,将dynamic类型直接转换成了String类型,这样解析就没有问题了。
JsonObject格式_嵌套JsonObject数据
前面的Json数据都不包含嵌套,下面给Json数据增加一层嵌套,data的value是一个JsonObject,代码如下所示。
{
"code": 0,
"result": "success",
"message": "message ok",
"data": {
"name": "xuyisheng",
"age": 18
}
}
那么针对嵌套的Json数据,首先要从嵌套的最里层逐步向外创建Model,这点和Gson生成Model非常类似。
因此,首先,需要生成Data Model,代码如下所示。
class Data {
String name;
int age;
Data.fromJson(Map<String, dynamic> json)
: name = json['name'],
age = json['age'];
@override
String toString() {
return 'name: $name ,age: $age';
}
}
这个非常简单,和第一部分的处理是一样的。
接下来,再来生成外层的Model,代码如下所示。
class BasicMapWithModel {
int code;
String result;
String message;
Data data;
BasicMapWithModel.fromJson(Map<String, dynamic> json)
: code = json['code'],
result = json['result'],
message = json['message'],
data = json['data'];
@override
String toString() {
return 'BasicModel: code: $code ,result: $result
message: $message ,data: ${data.toString()}';
}
}
运行下代码,你会发现又碰到了类型转换的错误,显然,问题同样出现在data的解析上,json[‘data’]返回的dynamic类型,所以需要转换为Data类型,因此,修改后的代码如下所示。
BasicMapWithModel.fromJson(Map<String, dynamic> json)
: code = json['code'],
result = json['result'],
message = json['message'],
data = Data.fromJson(json['data']);
通过类型转换,就完成了解析。
JsonObject格式_带有List类型JsonObject数据
在前一种数据格式的基础上,再进行进一步的嵌套,data中是一个JsonArray,数据如下所示。
{
"code": 0,
"result": "success",
"message": "message ok",
"data": [
{
"name": "xuyisheng",
"age": 18
},
{
"name": "zhujia",
"age": 3
}
]
}
对于这样的Json,处理方式和前面的一种基本类似,首先,从里层生成一个最基本的数据Model,可以随便命名,代码如下所示。
class Person {
String name;
int age;
Person.fromJson(Map<String, dynamic> json)
: name = json['name'],
age = json['age'];
@override
String toString() {
return 'name: $name ,age: $age';
}
}
再生成外面的数据Model,代码如下所示。
class BasicMapWithListModel {
int code;
String result;
String message;
List data;
BasicMapWithListModel.fromJson(Map<String, dynamic> json)
: code = json['code'],
result = json['result'],
message = json['message'],
data = (List.from(json['data']
.map((i) => Person.fromJson(i)).toList();
@override
String toString() {
return 'BasicModel: code: $code ,result: $result
message: $message ,data: ${data.toString()}';
}
}
这个地方就有点不好理解了,没关系,一步步来,首先,json[‘data’]返回的是一个List,每个元素实际上都是一个Person对象,所以,对于每一个元素,都需要使用Person的fromJson函数来进行转换,借助List的map操作符,就可以非常简单完成这一操作,经过转换之后,就完成了数据的解析工作。
JsonArray格式
前面几种Json数据格式最外层都是JsonObject,而下面这种,最外层就是一个JsonArray,数据如下所示。
[
{
"name": "xuyisheng",
"age": 18,
"sex": "M"
},
{
"name": "zhujia",
"age": 3,
"sex": "F"
}
]
对于这样的Json解析,可以参考下Android中的Json解析,首先,可以在最外面封装一个数据Model,其属性就是一个包含上面数据结构的List,首先,还是创建里层的数据Model,代码如下所示。
class Family {
String name;
String sex;
int age;
Family.fromJson(Map<String, dynamic> json)
: name = json['name'],
sex = json['sex'],
age = json['age'];
@override
String toString() {
return 'name: $name ,age: $age ,sex: $sex';
}
}
接下来,创建外层的数据Model,代码如下所示。
class BasicList {
List<Family> families;
BasicList.fromJson(List<dynamic> json)
: families = json.map((i) => Family.fromJson(i)).toList();
@override
String toString() {
return 'BasicList: $families';
}
}
这里要注意的是,构造函数接受的参数为List而非之前的Map,原因就是这种结构的Json通过dart:convert转换出来的是List。
总结
上面列举的Json类型基本上已经涵盖了平常开发中所遇到的Json数据格式,总结下Dart中进行Json转换的一般步骤。
- 确定最外层返回的是List还是Map
- 从最里层开始向外创建数据Model
- 对特定类型的数据进行数据类型转换
注意,有些开发者在创建数据Model的时候喜欢使用工厂函数,例如下面的代码。
BasicMapModel({this.code, this.result, this.message});
factory BasicMapModel.fromJson(Map<String, dynamic> json) {
return BasicMapWithListModel(
code: json['code'],
result: json['result'],
message: json['message'],
);
}
这种方式与本文所采用的方式并没有什么区别,只不过工厂函数可以更加灵活的控制实例的产生方式,所以在某些情况下,工厂函数会更加灵活,不过大部分情况下,使用普通的具名函数来创建实例就已经够了。
json_serializable
相比Android中的Json解析,Flutter的解析解析显得有些原始,原因在于Flutter不支持反射,所以无法像Gson那样通过反射来生成Json对象。
不过,回过头来想想在Flutter中的Json解析步骤,首先,需要把Json格式的字符串抽象成数据实体Model,这和在Android中使用Gson的步骤是一样的,只不过在Flutter中,多了一步生成fromJson函数的过程,而这个函数是非常简单的硬编码,即手动解析每个需要的字段,所以,这个过程也是可以通过脚本来自动化完成的,Flutter的开发团队也意识到了这一点,所以开发了json_serializable来帮助开发者完成这些繁杂的体力劳动。
配置json_serializable
首先,打开pubspec.yaml文件,增加json_annotation、build_runner和json_serializable的配置,如下所示。
dependencies:
flutter:
sdk: flutter
json_annotation: ^2.2.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.3.3
json_serializable: ^2.2.1
这里要注意的是,build_runner和json_serializable是放在dev_dependencies中的,它类似于Android中的debugCompile,也就是只在编译的时候使用,Release中是不会有这两个库的,它们仅仅是帮助开发者来生成代码。
这些库的最新版本,可以从Dart Pub上查找。
配置好之后,点击Android Studio上的Packages get、Packages upgrade或者在命令行中来执行这些指令来获取这些引用库。
示例
下面就通过一个例子来演示下如何使用json_serializable,首先,找到一个之前使用的Json,如下所示。
{
"code": 0,
"result": "success",
"message": "message ok",
"data": {
"name": "xuyisheng",
"age": 18
}
}
这个Json,前面已经设置好了数据实体,如下所示。
class BasicMapWithModel {
int code;
String result;
String message;
Data data;
}
class Data {
String name;
int age;
}
接下来,为了使用这个库,还需要给这个类添加一些代码,分为下面几个步骤。
首先,引入相关的依赖,代码如下所示。
import 'package:json_annotation/json_annotation.dart';
part 'TestJsonSerializable.g.dart';
TestJsonSerializable.g.dart文件就是稍后需要自动生成的文件,它的命名方式就是『数据实体的文件名.g.dart』。
接下来,给每个实体类增加注解,build_runner就是通过这个注解来寻找需要生成代码的类,如下所示。
@JsonSerializable()
然后给每个实体增加构造函数、fromJson和toJson函数,这里要注意的是,fromJson和toJson函数不需要具体实现,只需要生成函数名即可,具体的手动解析Json的过程,就是build_runner来生成的,代码如下所示。
TestJsonSerializable(this.code, this.result, this.message, this.data);
factory TestJsonSerializable.fromJson(Map<String, dynamic> srcJson) =>
_$TestJsonSerializableFromJson(srcJson);
Map<String, dynamic> toJson() => _$TestJsonSerializableToJson(this);
以最外层的Model为例,这里生成文件名的规则是『$数据实体类名FromJson』、『$数据实体类名ToJson』。全部的代码如下所示。
import 'package:json_annotation/json_annotation.dart';
part 'TestJsonSerializable.g.dart';
@JsonSerializable()
class TestJsonSerializable extends Object {
int code;
String result;
String message;
Data data;
TestJsonSerializable(this.code, this.result, this.message, this.data);
factory TestJsonSerializable.fromJson(Map<String, dynamic> srcJson) =>
_$TestJsonSerializableFromJson(srcJson);
Map<String, dynamic> toJson() => _$TestJsonSerializableToJson(this);
}
@JsonSerializable()
class Data extends Object {
String name;
int age;
Data(this.name, this.age);
factory Data.fromJson(Map<String, dynamic> srcJson) =>
_$DataFromJson(srcJson);
Map<String, dynamic> toJson() => _$DataToJson(this);
}
在编写这个代码的时候,由于需要的代码还没生成,所以编译器会报红警告,这是正常的,当代码生成后,这些警告自然就没有了。
最后,通过运行build_runner来生成所需要的代码,命令如下所示。
➜ flutter_json flutter packages pub run build_runner build
在项目目录下执行上面的指令即可,生成过程如下图所示。
➜ flutter_json flutter packages pub run build_runner build
[INFO] Generating build script...
[INFO] Generating build script completed, took 423ms
[INFO] Initializing inputs
[INFO] Reading cached asset graph...
[INFO] Reading cached asset graph completed, took 127ms
[INFO] Checking for updates since last build...
[INFO] Checking for updates since last build completed, took 977ms
[INFO] Running build...
[INFO] 1.0s elapsed, 0/1 actions completed.
[INFO] Running build completed, took 1.1s
[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 61ms
[INFO] Succeeded after 1.1s with 2 outputs (3 actions)
指令执行成功后,就会在实体类的当前目录下生成前面定义的『.g.dart』的文件。打开该文件,如下所示。
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'TestJsonSerializable.dart';
// ***********************************************************************
// JsonSerializableGenerator
// ***********************************************************************
TestJsonSerializable _$TestJsonSerializableFromJson(Map<String, dynamic> json) {
return TestJsonSerializable(
json['code'] as int,
json['result'] as String,
json['message'] as String,
json['data'] == null
? null
: Data.fromJson(json['data'] as Map<String, dynamic>));
}
Map<String, dynamic> _$TestJsonSerializableToJson(
TestJsonSerializable instance) =>
<String, dynamic>{
'code': instance.code,
'result': instance.result,
'message': instance.message,
'data': instance.data
};
Data _$DataFromJson(Map<String, dynamic> json) {
return Data(json['name'] as String, json['age'] as int);
}
Map<String, dynamic> _$DataToJson(Data instance) =>
<String, dynamic>{'name': instance.name, 'age': instance.age};
这些代码就是build_runner自动生成的解析Json的代码,至此,解析所需要的实体类就创建好了,现在回过头来看看,与之前手动解析Json自己写的那些方法,基本都是一样的,只不过这些机械的代码被build_runner自动生成了而已。
另外,build_runner也支持对文件的监听,来自动创建新生成的数据实体类,指令如下所示。
flutter packages pub run build_runner watch
进阶
json_annotation是类似Gson的注解,除了前面提到的@JsonSerializable()注解,其实还有很多其它注解,例如:@JsonKey(name="Json_Name"),即获取指定Json名的字段,类似Gson中的@SerializedName("Json_Name ")。更多的参数可以在json_annotation的Github上找到,这里不进行进一步的解释了,都是一些配置参数。
简化
虽然说官方提供了json_annotation、build_runner和json_serializable来简化Json解析实体类的生成,但是这个使用过程还是非常繁琐的,特别是创建好最简单的实体类后,还需要补充各种依赖、fromJson函数等内容,其实还是非常不方便的,但好在这些过程实际上也是非常机械的,同样可以通过一些方式来进行简化,Android Studio的Live Templates就是一个不错的选择,创建一个Live Templates,并增加要自动插入的代码,如图所示。
模板代码示例如下所示。
import 'package:json_annotation/json_annotation.dart';
part '$NAME$.g.dart';
@JsonSerializable()
class $NAME$ extends Object {
String $V1$;
String $V2$;
$NAME$(this.$V1$, this.$V2$);
factory $NAME$.fromJson(Map<String, dynamic> srcJson) =>
_$$$NAME$FromJson(srcJson);
Map<String, dynamic> toJson() => _$$$NAME$ToJson(this);
}
这就是一个最简单的数据实体类的模板代码了,在编辑器中输入创建的快捷键,就可以非常方便的使用这个快捷代码了,创建好之后,再通过build_runner就可以创建所需要的其它文件了。
这里需要注意的是,『$』在模板代码中表示的是变量,但这里需要将它作为文本处理,所以查阅idea的文档后发现,可以通过『$$』来进行转义,相关文档如下。
https://www.jetbrains.com/help/idea/template-variables.html

进一步简化
相比在Android中使用GsonFormat进行Json的实体类生成,Flutter中生成实体类的方式还是有些麻烦,因此,如果能够将GsonFormat的源码进行改造,实际上是完全可以直接通过Json生成实体类的。
当然,类似的工具还有很多,毕竟Json to Model在各个平台中都是需要的,类似的还有下面这个工具。
https://caijinglong.github.io/json2dart/index_ch.html
再例如下面这个工具,如图所示。
https://javiercbk.github.io/json_to_dart/
这些工具都可以完成这样的操作。
真·总结
本文从最基础的Flutter Json解析到一步步更加复杂的Json解析,再到更加高效的Json解析,一点点的让开发者了解如何在Flutter中处理Json。之所以没有直接讲解最高效的使用方法,是为了让开发者对Flutter中的Json解析有一个比较完整和深入的理解,这样在使用这些工具的时候才能知其所以然。