Flutter dio json解析

4,779 阅读3分钟

dio自带了个默认的转换器,查看源码会发现在DefaultTransformer 中的transformResponse函数中

if (responseBody != null &&
    responseBody.isNotEmpty &&
    options.responseType == ResponseType.json &&
    _isJsonMime(response.headers[Headers.contentTypeHeader]?.first)) {
  if (jsonDecodeCallback != null) {
    return jsonDecodeCallback(responseBody);
  } else {
    return json.decode(responseBody);
  }
}

里面会判断请求返回的数据的 ResponseType 如果是 json,会去判断有没有设置自定义json解析,如果没有会走dart默认的json解析;

所以 dio 发起网络请求如果不给设置泛型返回来的数据是经过json.decode解析的,会是一个 LinkedHashMap 类型。

但是如果接口返回的数据结构特别的复杂,dart默认的json解析用起来会特别的麻烦,而且默认是在UI线程解析json,数据量大又复杂可能会造成卡顿;

针对于复杂json解析会造成卡顿问题,dio给出的方案是使用compute在后台去解析json;
dio推荐的卡顿解决办法

// 必须是顶层函数
_parseAndDecode(String response) {
  return jsonDecode(response);
}

parseJson(String text) {
  return compute(_parseAndDecode, text);
}

void main() {
  ...
  // 自定义 jsonDecodeCallback
  (dio.transformer as DefaultTransformer).jsonDecodeCallback = parseJson;
  runApp(MyApp());
}

但是 dio 文档里依然是用的 dart 默认的 json 解析,返回来的是一个 LinkedHashMap 类型,使用起来很不爽;倒是可以自己封装一下,把Map中的值一个一个拿出来转换成 实体类,不过如果json 复杂起来这么做要累死;

使用 json_serializable (官方推荐)

json_serializable 就不一样了,它可以把 json 解析成实体对象,其实吧它的原理也是把Map里面的内容一个一个的拿出来,最终转换成实体对象,不过它提供了代码生成器,可以一个命令生成对应的处理代码,很省事;

使用方法

一、配置

pubspec.yaml 中加入依赖

dependencies:
  flutter:
    sdk: flutter
  dio: 3.0.9
  json_annotation: ^3.0.1
  
  
dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^1.10.0
  json_serializable: ^3.3.0

json_annotation,build_runner,json_serializable是使用 json_serializable需要加的,这里是文档,最新版本号去 pub.dev上看一下就行了。

比如这样的 json 先拿简单的来讲使用方法:

{
  "code": 200,
  "message": "hello"
}

需要写一个对应的实体类,比如这样:

// 注解要引入这个,json_serializable官方文档没有强调这个,很多文章也都忽略了这个,没有这个是无法通过命令生成代码的
import 'package:json_annotation/json_annotation.dart';

// 记得要加入这个,这个名字通常都是 '实体类的名字.g.dart'
part 'bean.g.dart';

// 要加上这个注解
@JsonSerializable()
class Bean {
  int code;
  String message;

  Bean();

  // 下面这两个模板方法,就按照官方文档这么定义就好,用来自动生成解析代码
  factory Bean.fromJson(Map<String, dynamic> json) => _$BeanFromJson(json);
  Map<String, dynamic> toJson() => _$BeanToJson(this);
}

还有一些注解去官方文档看一下就好

然后打开终端切换到项目根目录执行 pub run build_runner build,如果提示找不到pub命令就改成这样 flutter packages pub run build_runner build,完成之后再项目里就能看到生成的代码了;

生成后的代码:

part of 'bean.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Bean _$BeanFromJson(Map<String, dynamic> json) {
  return Bean()
    ..code = json['code'] as int
    ..message = json['message'] as String;
}

Map<String, dynamic> _$BeanToJson(Bean instance) => <String, dynamic>{
  'code': instance.code,
  'message': instance.message,
};

二、使用

接下来就可以在项目中直接使用了,

dio.get('url').then((response) {
    var bean = Bean.fromJson(response.data);
    ...
});

可以在网络请求之后手动的解析json,Bean.fromJson()需要的是Map类型参数,因为dio默认的响应转换器会使用dart的默认的json解析一遍,所以 response 里面的data已经是Map类型的;

当然如果返回的数据是复杂的json这样解析也可能会造成卡顿,这里就要用到上面提到过的 dio 推荐的方法,这只自定义的 json 解析回调,用 compute 在后台解析 json ;

// Must be top-level function
_parseAndDecode(String response) {
//  return jsonDecode(response);
  // 只是在这个位子替换掉 dart 默认的解析方式,换成 json_serializable 
  return Bean.fromJson(json.decode(response));
}

parseJson(String text) {
  return compute(_parseAndDecode, text);
}

这个时候网络请求返回的数据已经是自动解析过的了

dio.get('url').then((response) {
    var message = response.data.message
    ...
});

好了就这样 dio + json_serializable + 后台解析json就完事了;
不会写文章,写的很乱,主要是记录一下。

文中使用的 Flutter SDK 版本是 1.17.5
dio 3.0.9
json_serializable 3.3.0