使用声音空安全将多级JSON解析为class

Flutter parsing multilevel JSON to a class using sound null safety

我在 Android studio 中使用 FLutter,将多级 JSON 解析为 class。虽然我检查了很多 Whosebug post,但我找不到解决这个问题的方法。 这是我的 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"
    }
   ]
 }
] '

这些是我的 classes,使用 https://javiercbk.github.io/json_to_dart/ 生成。但是生成的代码不起作用(不是 null 安全)所以在阅读了许多 post 之后我想出了这些:

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>'

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

您的 JSON 数据是 List<dynamic> 因此 decodedJson 也是 List<dynamic>

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

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

如有任何疑问,请评论。

正如其他人指出的那样,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')}",
      };
}

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

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

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

您在调用 toJson 时将执行相同的操作。

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

分析器选项

当您在 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方法

这里是一个使用数据 class 的例子。 (为简洁起见,省略了导入和 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<String, dynamic> 类型。