Flutter 使用合理的 null 安全性将多级 JSON 解析为类

Posted

技术标签:

【中文标题】Flutter 使用合理的 null 安全性将多级 JSON 解析为类【英文标题】:Flutter parsing multilevel JSON to a class using sound null safety 【发布时间】:2021-08-01 11:43:27 【问题描述】:

我在 android Studio 中使用 FLutter,将多级 JSON 解析为一个类。尽管我检查了许多 *** 帖子,但我找不到解决此问题的方法。 这是我的 JSON

String myJson = '
[
 
  "sensor":"1234",
  "data":
   [
    
     "value":"40",
     "reading_time1":"2021-04-10"
    ,
    
     "value":"38",
     "reading_time1":"2021-04-11"
     
   ]
 ,
 
  "sensor":"1235",
  "data":
   [
    
     "value":"2",
     "reading_time1":"2021-04-23"
    ,
    
     "value":"39",
     "reading_time1":"2021-04-24"
    
   ]
 
] '

这些是我的类,使用 https://javiercbk.github.io/json_to_dart/ 生成。但是生成的代码不起作用(不是空安全)所以在阅读了很多帖子后我想出了这些:

class AllSensorData 

  String sensor="";
  List<Sensordata> sensordata = [];

  AllSensorData(required this.sensor,required this.sensordata);

  AllSensorData.fromJson(Map<String, dynamic> json) 
    sensor = json['sensor'];
    if (json['data'] != null) 
      sensordata = <Sensordata>[];
      json['data'].forEach((v) 
        sensordata.add(new Sensordata.fromJson(v));
      );
    
  

  Map<String, dynamic> toJson() 
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['sensor'] = this.sensor;
    if (this.sensordata != null) 
      data['data'] = this.sensordata.map((v) => v.toJson()).toList();
    
    return data;
  



class Sensordata 
  String value;
  String reading_time;

  Sensordata(
    required this.value,
    required this.reading_time
  );

  Sensordata.fromJson(Map<String, dynamic> json) :
    value = json['value'],
    reading_time = json['reading_time1'];

  Map<String, dynamic> toJson() 
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['value'] = this.value;
    data['reading_time'] = this.reading_time;
    return data;
  

现在当我跑步时:

var decodedJson = jsonDecode(myJson);
var jsonValue = AllSensorData.fromJson(decodedJson);

错误是:

type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>'

虽然我怀疑可能还有更多。感谢您的帮助。

【问题讨论】:

decodedJson 实际上是一个 SensorData 列表,而不仅仅是一个。您正在尝试将列表传递给 AllSensorData.fromJson 而它接受地图而不是列表。因此错误。确保其中至少有一个元素后尝试 AllSensorData(decodedJson[0])。 【参考方案1】:

对于其他来这里的人来说,这是一个稍微通用的答案。

分析仪选项

当您在 analysis_options.yaml 文件中将 implicit-dynamic 设置为 false 时,一开始可能很难解析 JSON。

analyzer:
  strong-mode:
    implicit-casts: false
    implicit-dynamic: false

如何种姓

如果您在 JSON 的顶层使用列表,则可以使用 as List 来强制类型:

import 'dart:convert';

void main() 
  final myList = json.decode(myJson) as List;
  for (final item in myList) 
    final name = item['name'] as String? ?? '';
    final age = item['age'] as int? ?? 0;
  


String myJson = '''
[
  
    "name":"bob",
    "age":22
  ,
  
    "name":"mary",
    "age":35
  
]
''';

这使您可以遍历每个项目。 item 本身就是 dynamic,但您仍然可以将其视为 Map,如果它为 null,则为其提供默认类型值。

使用fromJson 方法

这是一个使用数据类的示例。 (为简洁起见,省略了 import 和 myJson 字符串。)

void main() 
  final myList = json.decode(myJson) as List;
  for (final item in myList) 
    if (item is! Map<String, dynamic>) return; //      <-- interesting part
    final person = Person.fromJson(item);
  


class Person 
  final String name;
  final int age;

  Person(this.name, this.age);

  factory Person.fromJson(Map<String, dynamic> json) 
    final name = json['name'] as String? ?? '';
    final age = json['age'] as int? ?? 0;
    return Person(name, age);
  

这个几乎做同样的事情,除了在将映射传递给fromJson 方法之前,您检查类型。这允许 Dart 安全地将动态 item 提升为 fromJson 参数所需的 Map&lt;String, dynamic&gt; 类型。

【讨论】:

【参考方案2】:

您的代码大部分是正确的。您只需为decodedJson 的每个 元素调用fromJson ,因为它包含许多 sensorMaps 而不是一个。

List<Map<String,dynamic>> decodedJson = jsonDecode(myJson);

List<AllSensorData> sensorDataList = decodedJson.forEach(
  (sensorMap) => AllSensorData.fromJson(sensorMap)
);

在调用 toJson 时,您也会这样做。

【讨论】:

那是因为在你的类方法 fromJson 你没有指定返回类型。【参考方案3】:

正如其他人所指出的,JSON 有一个传感器数据列表,因此您需要对列表元素进行解码。我使用quicktype 生成(正确的)代码。

您说您正在使用null safety,但您的类中没有任何可选数据字段。如果数据中的字段是可选的,那么您可以使用quicktype 选项Make all properties optional。不幸的是,quicktype 似乎不知道dart 中的空安全性,因此您必须编辑其生成的代码以在数据字段声明中添加?,并在引用中添加!。我在下面做了这个:

import 'dart:convert';

List<Sensordata> sensordataFromJson(String str) =>
    List<Sensordata>.from(json.decode(str).map((x) => Sensordata.fromJson(x)));

String sensordataToJson(List<Sensordata> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Sensordata 
  Sensordata(
    this.sensor,
    this.data,
  );

  String? sensor;
  List<Datum>? data;

  factory Sensordata.fromJson(Map<String, dynamic> json) => Sensordata(
        sensor: json["sensor"] == null ? null : json["sensor"],
        data: json["data"] == null
            ? null
            : List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
      );

  Map<String, dynamic> toJson() => 
        "sensor": sensor == null ? null : sensor,
        "data": data == null
            ? null
            : List<dynamic>.from(data!.map((x) => x.toJson())),
      ;


class Datum 
  Datum(
    this.value,
    this.readingTime1,
  );

  String? value;
  DateTime? readingTime1;

  factory Datum.fromJson(Map<String, dynamic> json) => Datum(
        value: json["value"] == null ? null : json["value"],
        readingTime1: json["reading_time1"] == null
            ? null
            : DateTime.parse(json["reading_time1"]),
      );

  Map<String, dynamic> toJson() => 
        "value": value == null ? null : value,
        "reading_time1": readingTime1 == null
            ? null
            : "$readingTime1!.year.toString().padLeft(4, '0')-$readingTime1!.month.toString().padLeft(2, '0')-$readingTime1!.day.toString().padLeft(2, '0')",
      ;

【讨论】:

【参考方案4】:

您的 JSON 数据是 List&lt;dynamic&gt;,因此 decodedJson 也是 List&lt;dynamic&gt;

您必须从 decodedJson 的每个元素中解码 json。

  var decodedJson = jsonDecode(myJson);
  List<AllSensorData> list = [];
  for (Map<String, dynamic> json in decodedJson) 
    list.add(AllSensorData.fromJson(json));
  

如果您有任何疑问,请发表评论。

【讨论】:

感谢您的回答。我尝试使用代码,但在运行时出现错误:处理手势时抛出以下_TypeError:类型'Null'不是调试此行的'String'类型的子类型:list.add(AllSensorData. fromJson(json)); 当 AllSensorData 中的某个字段从 API 中获得 null 值时,会发生此错误。您需要检查 fromJson 方法中是否有任何值是否为 null 并根据该值返回。

以上是关于Flutter 使用合理的 null 安全性将多级 JSON 解析为类的主要内容,如果未能解决你的问题,请参考以下文章

snapshot.data.length 中的 Flutter Null 安全错误

如何使用具有 Null 安全性的 FlutterFire Firestore 和 Flutter 2.0 从 Firebase 获取数据

手把手教你将项目迁移到Flutter2.0 空安全

在 2.7.0 之前的 sdk 版本中,是不是需要 Flutter 来保证 null 安全性?

关闭之前 Flutter 项目的 Null Safety?

如何在 beta 通道上使用 Flutter 将 Dart 设置为稳定通道(以避免空安全选择加入)