• Dart中处理嵌套JSON


    前言

    由于Dart语言本身制定的一些标准(JSON和序列化),想在dart中找到类似Gson作用的库是做不到的,官方描述是这样的:

    Flutter 中是否有 GSON/Jackson/Moshi 的等价物?
    简单来说,没有。

    这样的库需要使用运行时进行 反射,这在 Flutter 中是被禁用的。运行时反射会影响 Dart 支持了相当久的 tree shaking(一种优化)。通过 tree shaking,你可以从你的发布版本中“抖掉”不需要使用的代码。这会显著优化 App 的体积。

    由于反射会默认让所有的代码被隐式使用,这让 tree shaking 变得困难。工具不知道哪一部分在运行时不会被用到,所以冗余的代码很难被清除。当使用反射时,App 的体积不能被轻易优化。

    而使用json_serializable 又稍显繁琐,后来决定还是依照gson等库的习惯自行编写一个转换类出来。

    至少初步达到在VSCode中配合json-to-dart-model插件处理嵌套JSON的目的。

    生成实体类

    假若JSON源数据如下(其实是聚合平台的免费笑话数据):

    {
        "error_code": 0,
        "reason": "Success",
        "result": {
            "data": [
                {
                    "content": "有一天晚上我俩一起吃西瓜,老大把西瓜籽很整洁的吐在了一张纸上,\r\n过了几天,我从教室回但宿舍看到老大在磕瓜子,\r\n我就问他:老大,你什么时候买的瓜子?\r\n老大说:刚晒好,说着抓了一把要递给我……",
                    "hashId": "bcc5fdc2fb6efc6db33fa242474f108a",
                    "unixtime": 1418814837,
                    "updatetime": "2014-12-17 19:13:57"
                },
                {
                    "content": ""我女朋友气跑了"\r\n"怎么回事?严重吗?你怎么着她了?"\r\n"不严重,我只是很久没用了"",
                    "hashId": "03a6095c18e1d6fe7e2c19b2a20d03d1",
                    "unixtime": 1418814837,
                    "updatetime": "2014-12-17 19:13:57"
                },
                {
                    "content": "还说神马来一场说走就走的旅行,\r\n工作后就连一场说走就走的下班都不行。",
                    "hashId": "10edf75c1e7d0933c91f0f39a28a2c84",
                    "unixtime": 1418814837,
                    "updatetime": "2014-12-17 19:13:57"
                }
            ]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    那么使用json-to-dart-model转化实体类后会生成以下三个类;
    最外层的JuheResponse:

    class JuheResponse{
      int? errorCode;
      String? reason;
      Result? result;
    
      JuheResponse({this.errorCode, this.reason, this.result});
    
      factory JuheJoke.fromJson(Map<String, dynamic> json) => JuheResponse(
            errorCode: json['error_code'] as int?,
            reason: json['reason'] as String?,
            result: json['result'] == null
                ? null
                : Result.fromJson(json['result'] as Map<String, dynamic>),
          );
    
      Map<String, dynamic> toJson() => {
            'error_code': errorCode,
            'reason': reason,
            'result': result?.toJson(),
          };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    第二层的Result:

    import 'datum.dart';
    
    class Result {
      List<Datum>? data;
    
      Result({this.data});
    
      factory Result.fromJson(Map<String, dynamic> json) => Result(
            data: (json['data'] as List<dynamic>?)
                ?.map((e) => Datum.fromJson(e as Map<String, dynamic>))
                .toList(),
          );
    
      Map<String, dynamic> toJson() => {
            'data': data?.map((e) => e.toJson()).toList(),
          };
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    最后一层的数据Datum,为了体现差异性,后面将类名改为Joke:

    class Datum {
      String? content;
      String? hashId;
      int? unixtime;
      String? updatetime;
    
      Datum({this.content, this.hashId, this.unixtime, this.updatetime});
    
      factory Datum.fromJson(Map<String, dynamic> json) => Datum(
            content: json['content'] as String?,
            hashId: json['hashId'] as String?,
            unixtime: json['unixtime'] as int?,
            updatetime: json['updatetime'] as String?,
          );
    
      Map<String, dynamic> toJson() => {
            'content': content,
            'hashId': hashId,
            'unixtime': unixtime,
            'updatetime': updatetime,
          };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    如果应用中只有Joke这么一个数据实体,使用以上的类来转换其实是没问题的;
    但问题就在于实际情况中,JuheResponse类下可能会套入不同的Result,而Result中又会有不同的Datum
    根据其他语言平台下的JSON序列库的经验,我们需要引入
    泛型
    ,要创建JuheResponse和Result

    引入泛型

    比如将JuheResponse中的fromJson方法改为:

      factory JuheResponse.fromJson(Map<String, dynamic> json) => JuheResponse(
            errorCode: json['error_code'] as int?,
            reason: json['reason'] as String?,
            result: json['result'] == null
                ? null
                : JSON().fromJson(json['result'] as Map<String, dynamic>,T) as T,
          );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将Result中的fromJson方法改为:

      factory Result.fromJson(Map<String, dynamic> json) => Result(
            data: (json['data'] as List<dynamic>?)
                ?.map((e) => JSON().fromJson(e as Map<String, dynamic>,T) as T)
                .toList(),
          );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样一来,不论嵌套的是任何类,都能将成功组装成应有的实体。
    那么初步设想中JSON工具类应该有这么一个方法:

    ///map或string转model实体,使用后需用as T转换相应的对象类型
      dynamic fromJson(dynamic value, Type type) {
    	// ...
        switch (type) {
          case JuheResponse<Result<Joke>>:
            return JuheResponse<Result<Joke>>.fromJson(value);
          case Result<Joke>:
            return Result<Joke>.fromJson(value);
          case Joke:
            return Joke.fromJson(value);
        }
        // ...
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    因为Dart的泛型类型检验相比Java要严格一些,所以必须声明准确的类名

    考虑到实体类中传入的value值往是Map,也就是经过json解码的,我们可以在此之前判断传入的值是否为原始字符串,如果是就转换一次:

        if (value is String) {
          //字符串需经过json解码为map格式
          value = json.decode(value);
        }
    
    • 1
    • 2
    • 3
    • 4

    这样一来就可以传入原始的字符串了;
    另外就是对List形式数据的处理,可以利用map来逐一转换并最终组合为List,需要注意是,
    如果传入的是type是List,那么就应该做进一步处理:

          case List<Joke>:
            return (value as List).map<Joke>((e) => fromJson(e, Joke)).toList();
    
    • 1
    • 2

    即每一步的数据类型都进行声明;
    这样一来,第二层的Result也可以随之改变,List可以变为T:

    class Result<T> {
      T? data;
    
      Result({this.data});
    
      factory Result.fromJson(Map<String, dynamic> json) => Result(
            data: JSON().fromJson(json['data'], T) as T,
          );
    
      Map<String, dynamic> toJson() => {
            'data': JSON().toJson(data, false),
          };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    与之对应的toJson也给予相似的处理。

    最终工具类

    考虑到字符串和map以及List的转换后,最终形成的工具类是这样的:

    class JSON {
      factory JSON() => _instance;
    
      JSON._internal();
    
      static final JSON _instance = JSON._internal();
    
      ///map或string转model实体,使用后需用as T转换相应的对象类型
      dynamic fromJson(dynamic content, Type type) {
        var value;
        if (content is String) {
          //字符串需经过json解码为map格式
          value = json.decode(content);
          if (value is List) {
            return fromJson(value, type);
          }
        } else if (content is Map<String, dynamic>) {
          //map形式直接使用
          value = content;
        } else if (content is List) {
          //List则嵌套调用自身即可
          value = content;
        } else {
          throw const FormatException("content is not json format");
        }
    
        switch (type) {
          case JuheResponse<Result<Joke>>:
            return JuheResponse<Result<Joke>>.fromJson(value);
          case Result<Joke>:
            return Result<Joke>.fromJson(value);
          case List<Joke>:
           	return (value as List).map<Joke>((e) => fromJson(e, Joke)).toList();
          case Joke:
            return Joke.fromJson(value);
          default:
            throw const FormatException("no model provided for content");
        }
        // return json;
      }
    
      ///实体转map或string
      dynamic toJson(dynamic data, bool bejson) {
        var result = bejson ? '' : Map<String, dynamic>();
        if (data is List) {
          return data.map((e) => toJson(e, bejson)).toList();
        } else {
          switch (data.runtimeType) {
            case Joke:
              result = (data as Joke).toJson();
              break;
            case Result<Joke>:
              result = (data as Result<Joke>).toJson();
              break;
            case JuheResponse<Result<Joke>>:
              result = (data as JuheResponse<Result<Joke>>).toJson();
              break;
            default:
              print("no model provided toJson");
          }
        }
        if (bejson) {
          result = json.encode(result);
        }
        return result;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    测试一下:

        Future(() async {
          return await rootBundle.loadString("/data/jokes.json");
        }).then((value) {
          JuheResponse<Result<Joke>> r =
              JSON().fromJsonAs<JuheResponse<Result<Joke>>>(value);
          setState(() {
            jokes = r.result!.data!;
            print(jokes[2].content);
            var s = JSON().toJson(jokes, false);
            //print(s);
            var jj = JSON().fromJson(s, List<Joke>);
            print(jj.length);
          });
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以上。

  • 相关阅读:
    序列化(二)Parcelable
    IPv4/IPv6、DHCP、网关、路由
    python的web开发框架有哪些
    不愧是阿里资深架构师,这本“分布式架构笔记”写得如此透彻明了
    c语言圣诞树
    C语言程序设计笔记(浙大翁恺版) 第十二周:程序结构
    使用双动态令牌混合器学习全局和局部动态以进行视觉识别
    探究Presto SQL引擎(3)-代码生成
    HTTP状态码的含义;并且实现:如何实现多组输入
    软件测试之TCP、UPD协议详解
  • 原文地址:https://blog.csdn.net/ifmylove2011/article/details/125887129