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"
            }
        ]
    }
}

那么使用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(),
      };
}

第二层的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(),
      };
}

最后一层的数据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,
      };
}

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

引入泛型

比如将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,
      );

将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(),
      );

这样一来,不论嵌套的是任何类,都能将成功组装成应有的实体。
那么初步设想中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);
    }
    // ...
  }

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

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

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

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

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

即每一步的数据类型都进行声明;
这样一来,第二层的Result也可以随之改变,List<Datum>可以变为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),
      };
}

与之对应的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;
  }
}

测试一下:

    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);
      });
    });

以上。

猜你喜欢

转载自blog.csdn.net/ifmylove2011/article/details/125887129