如果一个app不与服务器交互,获取一些结构化的数据是难以想象的,当开发一个网络app,迟早要消费一些json数据的.
这篇指南,就是告诉你在Flutter里使用json,包含了json在不同场景的解决方案.
术语:编码与序列化是一个事,转化结构化的数据为字符串,解码与反序列化也是一样的,是反向过程,转字符串为结构化数据.然后序列化通常是涉及了整个过程,转化数据到更方便使用的结构.
为了避免混淆,这篇文档,使用序列化,是指整个过程,编码与解码只是指相关的部分过程.
- Which JSON serialization method is right for me?
- Is there a GSON/Jackson/Moshi equivalent in Flutter?
- Serializing JSON manually using dart:convert
- Serializing JSON using code generation libraries
- Further references
哪种序列化的方法适合我?
这篇文章包含了两个策略来使用json
普通序列化.
使用代码生成器来自动序列化.
不同的项目有不同的复杂度与用例.小型的项目,使用生成器就太过了.有多个json模型的,较复杂的app,手动编码太耗事了而且容易出错.
小项目的普通序列化:
普通的解码,使用内置的json解码器,传输原始的json数据给json.decode()方法,然后在Map<String,dynamic>里面查找.没有额外的依赖,没有复杂的设置步骤.
普通的解码性能一般,当你的项目变的大的时候,手动解码就耗时易错了.如果不存在某些字段,还会出错.
如果没有很多数据模型在项目中,普通的序列化是可行的.可以查看示例:Serializing JSON manually using dart:convert.
使用代码生成器来处理大中型项目
json序列化,使用代码生成器,表示要使用额外的库来生成解码的类.需要一些设置,然后运行一个文件观察者来生成代码. json_serializable and built_value就是这样的库.
这种方式,可以容易地适应大项目.不易出错,但需要一些设置来初始化配置.
如果你的项目足够大,应该使用代码生成器来处理.示例:Serializing JSON using code generation libraries.
Flutter里面是否有GSON/JACKSON/MOSHI equivalent?
没有.
这些需要运行时的反射,而反射在Flutter里是不支持的.
大概是,使用反射,就无法在运行时检查未使用的代码,因为你不确定哪部分是不使用的,不利于app 的体积优化.所以就不要了.
dartson是可以使用反射,但不适用Flutter.
虽然在Flutter不可以使用反射,一些库也可以有简单的api来生成代码.可以查看:code generation libraries
使用dart:convert来实现普通的序列化.
Flutter里编码json是非常简单的,Flutter有一个内置的dart:convert库,包含了编解码.
一个示例:
{
"name": "John Smith",
"email": "john@example.com"
}
使用dart:convert,有两种方法:
内联:
查看:the dart:convert JSON documentation,是通过json.decode(json)方法来解码:
Map<String, dynamic> user = json.decode(json);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
不幸的是,它返回的是Map<String, dynamic>
,表示你不知道数据的类型,直到运行时.就会失去静态语言的特性,类型安全,自动完成,运行时异常检测等.代码就会不具鲁棒性.
例如,当你访问名字与邮箱,会引进typo,这只有在运行时才知道.
使用模型类来序列化
前面提到的问题,,可以通过转换为一个具体的模型类来处理.这些使用User类.
User.fromJson构造器,负责转换映射结构.
toJson方法,转user实例到map.
这种方式,调用者有类型安全,自动完成名字与邮箱域,有编译时的异常处理.如果是整型处理为字符,就会编译失败.
user.dart
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,
};
}
解码逻辑现在移到了User模型类里面了.可以很容易地解码
Map userMap = json.decode(json);
var user = new User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
要编码一个user,将user传入:不需要调用toJson方法,因为json.decode自动完成了
String json = json.encode(user);
这种方式,调用者不需要关心json的序列化了,模型类已经处理了这些.在生产的app中,你需要确定这种方式的正确性.fromJson,toJson都需要单元测试来验证.
然而,真实的场景中,不这么简单的,不像你可以使用这些数据.内嵌的json对象是很常见的.
使用代码生成器来序列化
虽然有其它的库可以使用,本指南使用的是 json_serializable package,自动生成代码.
序列化代码不是手写的,序列化的异常风险在运行时就降低了.
项目中配置:
包含json_serializable
包.一个普通依赖,两个dev依赖,dev依赖不被包含进app,只是开发时用到的环境.
最新的版本,可以在pubspec里面看到pubspec.yaml
dependencies:
# Your other regular dependencies here
json_annotation: ^0.2.3
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^0.9.0
json_serializable: ^0.5.4
运行Flutter packages get,来获取.
创建模型类
下面展示如何转换User到序列化中.这里使用上面用的模型类.
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()
/// Every json_serializable class must have the serializer mixin.
/// It makes the generated toJson() method to be usable for the class.
/// The mixin's name follows the source class, in this case, User.
class User extends Object with _$UserSerializerMixin {
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);
}
配置完,会生成编码与解码代码.对应上名字与邮箱.
也可以自定义命名策略.如,api返回的对象,是下划线的,你要转为驼几峰的.使用@JsonKey
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
这里有点类似gson了,不过它不是反射.
运行代码生成工具:
首先创建json_serializable类,会产生错误提示
因为还未产生代码.运行生成器生成代码就可以了.
有两种方式来运行:
一次性生成:
项目的根目录运行:flutter packages pub run build_runner build.会产生序列化代码.这触发一次性生成所有的源码.
这很方便,也很友好,如果你不需要修改类.
持续生成类:
一个观察者来监测我们的代码生成进程是很方便的,它检测我们项目的变化,然后生成必要的文件.通过在根目录里运行:flutter packages pub run build_runner watch
它是安全的,在后台运行.
消费json_serializable模型.
使用json_serializable
,解码一个json字符串,先前的代码不需要任何修改.
Map userMap = json.decode(json);
var user = User.fromJson(userMap);
编码也是
String json = json.encode(user);
有了json_serializable,你就忘记了普通的序列化吧.源码生成,创建了文件,叫user.g.dart的,有了所有的序列化逻辑,不再需要手动来写与单元测试了.由库来负责这些.