Search code examples
jsonflutterclassparsingdart-null-safety

Flutter parsing multilevel JSON to a class using sound null safety


I use FLutter in Android studio, parsing multilevel JSON to a class. Although I have checked many stackoverflow post I can not find a solution for this problem. This is my 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"
    }
   ]
 }
] '

These are my classes, generated using https://javiercbk.github.io/json_to_dart/ . But the generated code did not work (not null safety) so after reading many post I came up with these:

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

Now when I run:

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

The error is:

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

Although I suspect there might be more. Thanks for help.


Solution

  • As others have pointed out, the JSON has a list of sensor data, so you need to decode the list elements. I use quicktype to generate the (correct) code.

    You say you are using null safety, but you do not have any optional data fields in your classes. If the fields in the data are optional, then you can use the quicktype option Make all properties optional. Unfortunately quicktype does not seem to know about null safety in dart, so you have to edit its generated code to add ? on the data field declarations, and ! on the references. I did this below:

    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')}",
          };
    }