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

时间:2021-05-11 05:41:11

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

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

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

4 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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 方法

这是一个使用数据类的示例。 (为简洁起见,省略了导入和 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 提升为 Map<String, dynamic> 参数所需的 fromJson 类型。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

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

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

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