Flutter parsing multilevel JSON to a class using sound null safety
Solution 1
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')}",
};
}
Solution 2
This is a slightly more generic answer for other people coming here.
Analyzer options
When you have implicit-dynamic
set to false
in your analysis_options.yaml file, this can make it hard to parse JSON at first.
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
How to caste
If you are working with a list at the top level of your JSON, you can use as List
to caste the type:
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
}
]
''';
That allows you to loop though each item. The item
itself is dynamic
, but you can still treat it as a Map
and give it a default typed value if it is null.
Using a fromJson
method
Here is an example using a data class. (The import and myJson
string are omitted for brevity.)
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);
}
}
This one does mostly the same thing, except before passing the map into the fromJson
method, you check the type. That allows Dart to safely promote the dynamic item
to the Map<String, dynamic>
type needed by the fromJson
parameter.
Solution 3
Your JSON data is a List<dynamic>
hence decodedJson
is also List<dynamic>
.
You have to decode the json from each and every element of decodedJson
.
var decodedJson = jsonDecode(myJson);
List<AllSensorData> list = [];
for (Map<String, dynamic> json in decodedJson) {
list.add(AllSensorData.fromJson(json));
}
If you have any doubt comment it.
user7394563
Updated on December 29, 2022Comments
-
user7394563 over 1 year
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.
-
Afridi Kayal almost 3 yearsdecodedJson is actually a list of SensorData and not just one. You are trying to pass a list to AllSensorData.fromJson whereas it accepts a map and not a list. Hence the error. Try AllSensorData(decodedJson[0]) after ensuring there is at least one element in there.
-
-
user7394563 almost 3 yearsThanks for the answer. I tried to use the code, but during the runtime I get an error of: The following _TypeError was thrown while handling a gesture: type 'Null' is not a subtype of type 'String' debugging this line: list.add(AllSensorData.fromJson(json));
-
Abdur Rafay Saleem almost 3 yearsThat's because in your class method fromJson you didn't specify a return type.
-
Dhritiman Roy almost 3 yearsThis error occurs when one of the fields in the AllSensorData is given a value of null from the API. You need to check if any value is null or not in the fromJson method and return according to that.