JSON and serialization

https://flutter.io/json/

如果一个app不与服务器交互,获取一些结构化的数据是难以想象的,当开发一个网络app,迟早要消费一些json数据的.

这篇指南,就是告诉你在Flutter里使用json,包含了json在不同场景的解决方案.

术语:编码与序列化是一个事,转化结构化的数据为字符串,解码与反序列化也是一样的,是反向过程,转字符串为结构化数据.然后序列化通常是涉及了整个过程,转化数据到更方便使用的结构.

为了避免混淆,这篇文档,使用序列化,是指整个过程,编码与解码只是指相关的部分过程.

哪种序列化的方法适合我?

这篇文章包含了两个策略来使用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类,会产生错误提示

IDE warning when the generated code for a model class does not exist yet.

因为还未产生代码.运行生成器生成代码就可以了.

有两种方式来运行:

一次性生成:

项目的根目录运行: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的,有了所有的序列化逻辑,不再需要手动来写与单元测试了.由库来负责这些.

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值