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

我在 Android Studio 中使用 FLutter,将多级 JSON 解析为一个类。尽管我检查了许多 stackoverflow 帖子,但我找不到解决此问题的方法。 这是我的 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])。

标签: json flutter class parsing dart-null-safety


【解决方案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 并根据该值返回。
      猜你喜欢
      • 2021-11-12
      • 1970-01-01
      • 1970-01-01
      • 2021-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      相关资源
      最近更新 更多