Flutter的Json数据解析之FlutterJsonBeanFactory插件

一、FlutterJsonBeanFactory

Flutter 使用的是 Dart 语言进行开发,而 Dart 语言没有反射,所以无法像 Java 一样通过反射直接将 Json 数据映射为对应的对象实体类对象。官方解决方案是将 Json 数据转换为字典,然后从字典中进行取数使用。但直接从字典中取数很不方便,写代码时没有自动提示很不友好,而且可能在写的时候写错字段名。

我们可以将 Json 转换为字典后再映射到对象实体字段里,这样使用时就可以直接使用对应实体类对象。而FlutterJsonBean就可以帮我们自动生成映射代码。

二、插件安装

插件市场搜索FlutterJsonBeanFactory,安转后重启AS。我安装的版本是4.4.5,实例代码也是基于此版本的分析。
并且可以在Setting->Tools->FlutterJsonBeanFactory里边自定义实体类的后缀,默认是entity。

三、创建实体类

复制json到粘贴板,右键自己要存放实体的目录,可以看到JsonToDartBeanAction


image.png

Class Name是实体名字,会默认加上entity
JSON TextJson文本
null-able勾选后所有属性都是可空的?,不勾选都会加上late,延迟初始化

image.png

执行Make后生成代码目录如下:
models项目自建,存放实体
generated/json是插件生成目录,xx_entity.g.daet是实体类生成的辅助类方法, base是存放基础公共代码

image.png

1.xx_entity.dart

@JsonSerializable()
class UserEntity {
  String? id;
  String? name;
  String? age;

  UserEntity();

  factory UserEntity.fromJson(Map<String, dynamic> json) =>
      $UserEntityFromJson(json);

  Map<String, dynamic> toJson() => $UserEntityToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}

会生成fromJson的工厂方法和toJson方法,分别调用xx_entity.g.dart中的$xxEntityFromJson方法和$xxEntityToJson方法。toSring方法会把对象转json字符串显示。

如果是修改了实体类,鼠标悬停在gernerated目录上,执行Alt+J快捷键,就会自动生成新的映射代码,并且去除多余的。

2.xx_entity.g.dart

实体类对应的辅助方法文件,g.dart是文件的后缀,存放在generated/json目录下。
主要包含$xxEntityFromJson$xxEntityToJson方法,$+实体类名为前缀。

$xxFromJson 将 Json 数据的对应字段取出来然后赋值给实体类的对应字段。Json 数据转换为实体字段使用了 jsonConvert.convert 其定义在 json_convert_content.dart 中。
$xxToJson将实体数据转换为 Map 字典。

UserEntity $UserEntityFromJson(Map<String, dynamic> json) {
    final UserEntity userEntity = UserEntity();
    final String? id = jsonConvert.convert<String>(json['id']);
    if (id != null) {
        userEntity.id = id;
    }
    final String? user = jsonConvert.convert<String>(json['user']);
    if (user != null) {
        userEntity.user = user;
    }
    final String? age = jsonConvert.convert<String>(json['age']);
    if (age != null) {
        userEntity.age = age;
    }
    final int? sex = jsonConvert.convert<int>(json['sex']);
    if (sex != null) {
        userEntity.sex = sex;
    }
    return userEntity;
}

Map<String, dynamic> $UserEntityToJson(UserEntity entity) {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id'] = entity.id;
    data['user'] = entity.user;
    data['age'] = entity.age;
    data['sex'] = entity.sex;
    return data;
}

3.json_convert_content.dart

json_convert_content.dartJsonConvert类, 用于统一进行 Json 与实体类的转换。

JsonConvert jsonConvert = JsonConvert();
typedef JsonConvertFunction<T> = T Function(Map<String, dynamic> json);

class JsonConvert {
  static final Map<String, JsonConvertFunction> _convertFuncMap = {
      (UserEntity).toString(): UserEntity.fromJson,
  };

  T? convert<T>(dynamic value) {...}

  List<T?>? convertList<T>(List<dynamic>? value) {...}

  List<T>? convertListNotNull<T>(dynamic value)  {...}

  T? asT<T extends Object?>(dynamic value) {...}

  //list is returned by type
  static M? _getListChildType<M>(List<Map<String, dynamic>> data) {...}

  static M? fromJsonAsT<M>(dynamic json)  {...}
}
convert

将json数据转换为实体对象。首先判断了传入的数据是否为null,为null则直接返回null, 不为空则调用asT方法。在生成的.g.dart$xxEntityFromJson方法中非 List 类型字段基本都是调用 convert 方法进行转换。

  T? convert<T>(dynamic value) {
    if (value == null) {
      return null;
    }
    return asT<T>(value);
  }
convertList

将json数据转换为实体对象List。首先也是判断了传入的数据是否为null ,为null则直接返回null , 不为空则遍历value使用map调用asT方法进行转换,最终还是调用的asT方法。在转换上加了try-catch,如果报错则返回空的List

  List<T?>? convertList<T>(List<dynamic>? value) {
    if (value == null) {
      return null;
    }
    try {
      return value.map((dynamic e) => asT<T>(e)).toList();
    } catch (e, stackTrace) {
      debugPrint('asT<$T> $e $stackTrace');
      return <T>[];
    }
  }
converListNotNull

convertList的区别是参数不一样,convertList参数传入的是List<dynamic>convertListNotNull传入的直接是dynamic。其次最大的区别是调用asT方法时convertListNotNull在 asT 后面加了一个 !,表示不为空。
当在实体类里定义字段为List类型时,会根据是否为List中元素的非空类型而选择生成 convertListconvertListNotNull来进行转换,非空采用convertListNotNull,可空采用convertList

  • List<String?>? foodList1;采用convertList
  • List<String>? foodList2;采用convertListNotNull
  • late List<String?> foodList3;采用convertList
  • late List<String> foodList4;采用convertListNotNull
  List<T>? convertListNotNull<T>(dynamic value) {
    if (value == null) {
      return null;
    }
    try {
      return (value as List<dynamic>).map((dynamic e) => asT<T>(e)!).toList();
    } catch (e, stackTrace) {
      debugPrint('asT<$T> $e $stackTrace');
      return <T>[];
    }
  }
as<T>

首先判断传入的数据类型是否为要转换的数据类型,如果是的话就直接返回传入参数,即如果要将传入数据转换为User,但是传入参数本身就是User类型,那就直接返回。
然后通过 T.String()获取泛型类型的名称,再与StringintdoubleDateTimebool这些基础数据类型进行比较,如果是这些类型则调用这些类型的转换方法进行转换。
最后,如果不是基础类型则去_convertFuncMap寻找其它的实体类型,并调用其value,即fromJson方法,类似递归的去解析。

  T? asT<T extends Object?>(dynamic value) {
    if (value is T) {
      return value;
    }
    final String type = T.toString();
    try {
      final String valueS = value.toString();
      if (type == "String") {
        return valueS as T;
      } else if (type == "int") {
        final int? intValue = int.tryParse(valueS);
        if (intValue == null) {
          return double.tryParse(valueS)?.toInt() as T?;
        } else {
          return intValue as T;
        }
      } else if (type == "double") {
        return double.parse(valueS) as T;
      } else if (type == "DateTime") {
        return DateTime.parse(valueS) as T;
      } else if (type == "bool") {
        if (valueS == '0' || valueS == '1') {
          return (valueS == '1') as T;
        }
        return (valueS == 'true') as T;
      } else if (type == "Map" || type.startsWith("Map<")) {
        return value as T;
      } else {
        if (_convertFuncMap.containsKey(type)) {
          return _convertFuncMap[type]!(value) as T;
        } else {
          throw UnimplementedError('$type unimplemented');
        }
      }
    } catch (e, stackTrace) {
      debugPrint('asT<$T> $e $stackTrace');
      return null;
    }
  }
fromJsonAsT

判断传入Json数据是否为null,为null则直接返回null。然后判断Json数据是否为List,是 List则调用_getListChildType否则通过全局变量jsonConvert调用asT

  static M? fromJsonAsT<M>(dynamic json) {
    if (json is List) {
      return _getListChildType<M>(
          json.map((e) => e as Map<String, dynamic>).toList());
    } else {
      return jsonConvert.asT<M>(json);
    }
  }
_getListChildType

<UserEntity>[] is M直接创建对应实体类的空 List 判断是否为泛型类型,如果类型相同,则通过map调用对应实体类的 fromJson方法进行转换.

  static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
    if (<UserEntity>[] is M) {
      return data
          .map<UserEntity>((Map<String, dynamic> e) => UserEntity.fromJson(e))
          .toList() as M;
    }

    debugPrint("${M.toString()} not found");

    return null;
  }

4.json_field.dart

JsonSerializable类注解,二次生成代码时插件查找该注解的类进行生成。
JSONField字段注解,用于自定义字段映射和配置是否序列化和反序列化字段。

class JsonSerializable{
    const JsonSerializable();
}

class JSONField {
  //Specify the parse field name
  final String? name;

  //Whether to participate in toJson
  final bool? serialize;
  
  //Whether to participate in fromMap
  final bool? deserialize;

  const JSONField({this.name, this.serialize, this.deserialize});
}

四、使用

1.单实体解析

直接调用实体类对应的``fromJson```方法即可将 Json 数据解析为实体对象。

UserEntity? user;  
String userData = """
     {
        "id":"1",
        "name":"qi",
        "age":22
     }
    """;

user = UserEntity.fromJson(jsonDecode(userData));

调用生成的JsonConvert去解析,使用convertasTfromJsonAsT都能得到结果

user = jsonConvert.convert<UserEntity>(jsonDecode(userData));

user = jsonConvert.asT<UserEntity>(jsonDecode(userData));

user = JsonConvert.fromJsonAsT<UserEntity>(jsonDecode(userData));

2.List解析

解析 Json List 数据则需要调用 JsonConvert 的对应方法进行解析,除了使用上面的 convertasTfromJsonAsT 外,还可以使用 convertListconvertListNotNull

List<UserEntity>? users;
List<UserEntity?>? userNulls;

users = jsonConvert.convert<List<UserEntity>>(jsonDecode(userData));

users = jsonConvert.asT<List<UserEntity>>(jsonDecode(userData));

users = JsonConvert.fromJsonAsT<List<UserEntity>>(jsonDecode(userData));

users = jsonConvert.convertListNotNull<UserEntity>(jsonDecode(userData));

userNulls = jsonConvert.convertList<UserEntity>(jsonDecode(userData));

convertListconvertListNotNullconvertasTfromJsonAsT 的区别在于前者的泛型为 List Item元素的泛型类型,后者则直接为对应 List 的类型。如上面 convertListconvertListNotNull的泛型直接为UserEntity , 而 convertasTfromJsonAsT 的泛型为List<UserEntity>

3.JSONField的使用

自定义字段名

处理Json数据字段和实体属性字段不一致的问题,如后台返回Json命名不规范这种情况。可以用JSONField
自定义字段映射。如后台返回 AGE就可以如下使用,映射成age。加完之后照样需要执行Alt+J

  @JSONField(name: "AGE")
  String? age;
忽略字段

JSONField 还有两个字段 serializedeserialize 用于序列化和反序列化时忽略某个字段。

五、优化

后台返回的数据一般是经过一层包装

{
  "code": 200,
  "message": "success",
  "data":{
    "id": "1",
    "name": "qi1",
    "age": 18
  }
}

而重新用插件生成会生成如下代码:

@JsonSerializable()
class ApiResponseEntity {

    int? code;
    String? message;
    ApiResponseData? data;
  
  ApiResponseEntity();

  factory ApiResponseEntity.fromJson(Map<String, dynamic> json) => $ApiResponseEntityFromJson(json);

  Map<String, dynamic> toJson() => $ApiResponseEntityToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}

@JsonSerializable()
class ApiResponseData {

    String? id;
    String? name;
    int? age;
  
  ApiResponseData();

  factory ApiResponseData.fromJson(Map<String, dynamic> json) => $ApiResponseDataFromJson(json);

  Map<String, dynamic> toJson() => $ApiResponseDataToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}

要死这样,每一个接口的都有一个ResponseEntity,使用起来不便于统一封装。
所以我们可以把ApiResponseData换成 dynamic,文件底部的ApiResponseData信息也全部删除,再执行Alt+J,这样就会自动清理掉整理json_convert_content.dartapi_response_entity.g.dart中的ApiResponseData痕迹。再把dynamic替换成T,并且去除顶部的@JsonSerializable(),避免下次执行Alt+J,替换掉自己的自定义。

@JsonSerializable()
class ApiResponseEntity<T> {
  late int code;
  late String message;
  late T data;

  ApiResponseEntity();

  factory ApiResponseEntity.fromJson(Map<String, dynamic> json) =>
      $ApiResponseEntityFromJson<T>(json);

  Map<String, dynamic> toJson() => $ApiResponseEntityToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}
ApiResponseEntity<T> $ApiResponseEntityFromJson<T>(Map<String, dynamic> json) {
  final ApiResponseEntity<T> apiResponseEntity = ApiResponseEntity<T>();
  final int? code = jsonConvert.convert<int>(json['code']);
  if (code != null) {
    apiResponseEntity.code = code;
  }
  final String? message = jsonConvert.convert<String>(json['message']);
  if (message != null) {
    apiResponseEntity.message = message;
  }
  final T data = jsonConvert.convert<dynamic>(json['data']);
  if (data != null) {
    apiResponseEntity.data = data;
  }
  return apiResponseEntity;
}

Map<String, dynamic> $ApiResponseEntityToJson(ApiResponseEntity entity) {
  final Map<String, dynamic> data = <String, dynamic>{};
  data['code'] = entity.code;
  data['message'] = entity.message;
  data['data'] = entity.data;
  return data;
}

并且把api_response_entity.g.dart移除generated目录,因为那个目录会自动删除无用的文件。可以和api_reponse_entity.dart单独存放在一个文件夹当中。

优化后使用

第一次发现,reponse的data是null。因为新的插件在 asT方法没有去调用fromJsonAsT,这个需要我们自加上,否则会失败。

if (_convertFuncMap.containsKey(type)) {
  return _convertFuncMap[type]!(value) as T;
} else {
  return fromJsonAsT<T>(value);
  // throw UnimplementedError('$type unimplemented');
}
  //单实体
  String responseData1 = """
    {
      "code": 200,
      "message": "success",
      "data":{
        "id": 1,
        "name": "qi1",
        "age": 21
      }
    }
    """;

  //List
  String responseData2 = """
    {
      "code": 200,
      "message": "success",
      "data":[
        {
          "id": 1,
          "name": "qi1",
          "age": 21
        },{
          "id": 2,
          "name": "qi2",
          "age": 22
        }
      ]
    }
    """;

//基础数据类型
  String responseData3 = """
    {
      "code": 200,
      "message": "success",
      "data": 18
    }
    """;

  _apiResponseDecode() {
    setState(() {
      response1 = ApiResponseEntity.fromJson(jsonDecode(responseData1));
      response2 = ApiResponseEntity.fromJson(jsonDecode(responseData2));
      response3 = ApiResponseEntity.fromJson(jsonDecode(responseData3));
    });
  }

  _getApiResponseContent() {
    return response1.toString() +
        "\n" +
        response2.toString() +
        "\n" +
        response3.toString();
  }

参考链接:https://juejin.cn/post/7043721908801503269

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容