How to parse dynamic JSON keys in dart - json

I have an API that returns JSON with dynamic keys, i.e. the key value changes on every GET request.
Ex: This is the format of the JSON.
I have a book model in dart,
class Book {
String id = "";
String title = "";
Book();
Book.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
}
static List<Book> listFromJson(List<dynamic> json) {
return json.map((val) => Book.fromJson(val)).toList();
}
}
and JSON response,
{
"Recommended For You" : [
{
"id" : "001",
"title" : "title001"
},
...
],
"Thrillers" : [
{
"id" : "005",
"title" : "title005"
},
...
]
}
How to parse this into lists of books according to the genre, given that genres (like Thrillers) can change(i.e. it is dynamic and may change for each user)?
Edit 1
Thrillers and Recommended For You aren't constant, some times it may change to Horror and Romantic or something else. It is dynamic
Edit 2 : The solution
Thanks to #lrn's solution, I came up with a new Class
class Recommendations {
String recommendationType;
List<Book> recommendations;
#override
String toString() {
return 'Recomendations[title: $recommendationType]';
}
Recommendations.fromJson(MapEntry<String, dynamic> json) {
if (json == null) return;
this.recommendationType = json.key;
this.recommendations = Book.listFromJson(json.value);
}
static List<Recommendations> listFromJson(Map<String, dynamic> json) {
return json == null
? List<Recommendations>.empty()
: json.entries
.map((value) => new Recommendations.fromJson(value))
.toList();
}
}

If you don't know the keys, but do know that it's a JSON map where the values are the book lists you're looking for, I'd write:
List<Book> parseCategorizedBooksJson(Map<String, dynamic> json) =>
[for (var books in json.values) ...Book.listFromJson(books)];
or
List<Book> parseCategorizedBooksJson(Map<String, dynamic> json) =>
[for (var books in json.values)
for (var book in books) Book.fromJson(book)];
(which avoids building intermediate lists of books).
With this you simply iterate the values of the outer map, and ignore the keys. JSON maps are just plain Dart Maps and you can access the keys as .keys (and then go trough them without needing to known them ahead of time) and access the values as .values (and completely ignore the keys).
Each value is a list of books, which you can parse using your existing static functions.
To include the genre, you have to say how you want the genre remembered. The simplest is to retain the data as a map:
var categoriedBooks = {for (var genre in json)
genre: Book.listFromJson(json[genre])
};
This builds a map with the same keys (the genres), but where the values are now lists of Books.

Your response model contains two different arrays, recommendedForYou and thrillers. So, you can handle it like this:
// To parse this JSON data, do
//
// final responseModel = responseModelFromJson(jsonString);
import 'dart:convert';
ResponseModel responseModelFromJson(String str) => ResponseModel.fromJson(json.decode(str));
String responseModelToJson(ResponseModel data) => json.encode(data.toJson());
class ResponseModel {
ResponseModel({
this.recommendedForYou,
this.thrillers,
});
List<Book> recommendedForYou;
List<Book> thrillers;
factory ResponseModel.fromJson(Map<String, dynamic> json) => ResponseModel(
recommendedForYou: json["Recommended For You"] == null ? null : List<Book>.from(json["Recommended For You"].map((x) => Book.fromJson(x))),
thrillers: json["Thrillers"] == null ? null : List<Book>.from(json["Thrillers"].map((x) => Book.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"Recommended For You": recommendedForYou == null ? null : List<dynamic>.from(recommendedForYou.map((x) => x.toJson())),
"Thrillers": thrillers == null ? null : List<dynamic>.from(thrillers.map((x) => x.toJson())),
};
}
class Book {
Book({
this.id,
this.title,
});
String id;
String title;
factory Book.fromJson(Map<String, dynamic> json) => Book(
id: json["id"] == null ? null : json["id"],
title: json["title"] == null ? null : json["title"],
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"title": title == null ? null : title,
};
}
You can use this site to convert your json to any dart model.

Related

NoSuchMethodError : The method 'map' was called on null. Plz Provide the Solution

I am trying to parse data from a Rest API inside a Dart/Flutter application.
The JSON contains a field called data at the root, which contains a list of Words.
I want to get a List<ArticalList> from this JSON giving json["data"].map((x) => ArticalList.fromJson(x))
I already have the following code:
import 'dart:convert';
Welcome welcomeFromJson(String str) => Welcome.fromJson(json.decode(str));
String welcomeToJson(Welcome data) => json.encode(data.toJson());
class Welcome {
Welcome({
required this.code,
required this.status,
required this.message,
required this.data,
});
final int code;
final String status;
final String message;
final List<ArticalList> data;
factory Welcome.fromJson(Map<String, dynamic> json) => Welcome(
code: json["code"] ?? 0,
status: json["status"] ?? '',
message: json["message"] ?? '',
data: List<ArticalList>.from(json["data"].map((x) => ArticalList.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"code": code,
"status": status,
"message": message,
"data": List<dynamic>.from(data.map((x) => x.toJson())),
};
}
class ArticalList {
ArticalList({
required this.id,
required this.title,
required this.detail,
required this.image,
});
int id;
String title;
String detail;
String image;
factory ArticalList.fromJson(Map<String, dynamic> json) => ArticalList(
id: json["id"] == null ? 0 : json["id"],
title: json["title"] == null ? '' : json["title"],
detail: json["detail"] == null ? '' : json["detail"],
image: json["image"] ?? 'http://eduteksolutions.in/images/logo.jpeg',
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"title": title == null ? null : title,
"detail": detail == null ? null : detail,
"image": image,
};
}
I think your getting error at
data: List<ArticalList>.from(json["data"].map((x) => ArticalList.fromJson(x))),
I create a function with null safety which accept map and return object list
static List<ArticalList> toListFormMap({
Map? map,
}) {
if (map?.isEmpty ?? true) return [];
List<ArticalList> items = [];
map?.forEach((key, data) {
final ArticalList item = ArticalList.fromJson(data);
items.add(item);
});
return items;
}
and same method which convert map to Map
static Map toMapList(List<ArticalList>? items) {
Map map = {};
items?.forEach((element) {
map[element.id] = element.toJson();
});
return map;
}
for both case it handle null data error and also convert Object List to Map list and Map list to Object list.
I hope it will be helpful.

How to check if JSON array is empty in Flutter

Trying to create an if statement that checks if an API returns 'items' containing data
Here is the API url, you can see that items is empty for that specific data query https://data.food.gov.uk/food-alerts/id?since=2021-01-04T00:00:00Z
My code is below, been trying to see if I can check 'items' to see if it is null, as well as checking for its length and see if it equals 0 but has not worked so far
class AllergyAlert {
final String title;
AllergyAlert({this.title});
factory AllergyAlert.fromJson(Map<String, dynamic> json) {
if (json['items'] == null) {
return AllergyAlert(
title: 'No new allergy alerts',
);
} else {
return AllergyAlert(
title: json['items'][0]['title'],
);
}
}
}
You can try this
return AllergyAlert(
title: json['items'].isEmpty ? 'No new allergy alerts' : json['items'][0]['title'],
);
First create a class to decode your json string with. You can paste your json here Quicktype
This can then be used with a FutureBuilder to load the data. Here is a full example
Solution
Then after that you will just use the defined names to check. Like:
class Allergen {
Allergen({
this.meta,
this.items,
});
final Meta meta;
final List<dynamic> items;
factory Allergen.fromJson(Map<String, dynamic> json) => Allergen(
meta: Meta.fromJson(json["meta"]),
items: List<dynamic>.from(json["items"].map((x) => x)),
);
Map<String, dynamic> toJson() => {
"meta": meta.toJson(),
"items": List<dynamic>.from(items.map((x) => x)),
};
}
class Meta {
Meta({...//more here
you can just check the length of the list
Allergen.items.length==0?//Do This://Else This

flutter dart: how to encode a map / dictionary to JSON

I managed to decode a JSON dictionary but how can I do the opposite - encoding?
class Achievements {
Achievements({this.skills});
var skills = Map<String, SkillHist>();
// ==================================================ENCODING DICTIONARY
factory Achievements.fromJson(Map<String, dynamic> json) {
var innerMap = json['skills'];
var skillMap = Map<String, SkillHist>();
innerMap.forEach((key, value) {
skillMap.addAll({key: SkillHist.fromJson(value)});
});
return Achievements(
skills: skillMap,
);
}
// ==================================================DECODING DICTIONARY
Map<String, dynamic> toJson() => {
"skills": skills == null ? null : Map // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
};
}
class SkillHist {
SkillHist({this.currentSkill, this.skillLevel, this.skillEarnedDate});
final int currentSkill;
final List<int> skillLevel;
final List<DateTime> skillEarnedDate;
factory SkillHist.fromJson(Map<String, dynamic> json) {
return SkillHist(
currentSkill: json["currentSkill"] == null ? null : json['currentSkill'],
skillLevel: json["skillLevel"] == null ? null : List<int>.from(json["skillLevel"].map((x) => x)),
skillEarnedDate: json["skillEarnedDate"] == null ? null : List<DateTime>.from(json["skillEarnedDate"].map((x) => x)),
);
}
Map<String, dynamic> toJson() => {
"currentSkill": currentSkill == null ? null : currentSkill,
"skillLevel": skillLevel == null ? null : List<int>.from(skillLevel.map((x) => x)),
"skillEarnedDate": skillEarnedDate == null ? null : List<DateTime>.from(skillEarnedDate.map((x) => x)),
};
}
With this issue in mind, I also started wondering how I implemented the decoding of a List, which I copied from some snippet, months ago.
Why am I using
"skillEarnedDate": List<DateTime>.from(skillEarnedDate.map((x) => x)),
and not simply
"skillEarnedDate": skillEarnedDate,
skillEarnedDate is a List already, so why do I have initiate a new one?

Flutter converting Nested Object from Json returns null

I have a Nested Object like this just a bit bigger:
"name": "String",
"exercise": [
{
"index": 1,
}
],
"pause": [
{"index":2},
]
I convert the exercise and pause to a Json String and save them in a column in SQFLite.
The problem
When I read the Data everything works fine including the List (not nested) but both list's of nested Object are empty when I read a value of the nested object it gives an error.
item.exercise[0].index.toString()
Valid Value range is empty: 0
When I read only item.exercise.toString() it returns []. Without != null ? [...] : List<Exercise>() it also throws an error
Data I get from my Database (shortened)
List of:
[{name: number 1, id: 56, exercise: [{"index":1,"weightGoal":[15,16,17]}, {"index":3,"weightGoal":[15,16,17]}], pause: [{"index":2}]},{"index":4}]}]
What I do with it
Here I try to go through the list and convert it into a List of PlanModel:
List<PlanModel> list =
res.isNotEmpty ? res.map((c) => PlanModel.fromJson(c)).toList() : [];
return list;
Full model
PlanModel planModelFromJson(String str) => PlanModel.fromJson(json.decode(str));
String planModelToJson(PlanModel data) => json.encode(data.toJson());
class PlanModel {
PlanModel({
this.name,
this.id,
this.workoutDays,
this.pastId,
this.timesDone,
this.exercise,
this.pause,
});
String name;
int id;
List<String> workoutDays;
int pastId;
int timesDone;
List<Exercise> exercise;
List<Pause> pause;
factory PlanModel.fromJson(Map<String, dynamic> json) => PlanModel(
name: json["name"],
id: json["id"],
workoutDays: List<String>.from(jsonDecode(json["workoutDays"])),
pastId: json["pastId"],
timesDone: json["timesDone"],
exercise: json["Exercise"] != null ? new List<Exercise>.from(json["Exercise"].map((x) => Exercise.fromJson(x))): List<Exercise>(),
pause: json["Pause"] != null ? new List<Pause>.from(json["Pause"].map((x) => Pause.fromJson(x))): List<Pause>(),
);
Map<String, dynamic> toJson() => {
"name": name,
"id": id,
"workoutDays": List<dynamic>.from(workoutDays.map((x) => x)),
"pastId": pastId,
"timesDone": timesDone,
"Exercise": List<dynamic>.from(exercise.map((x) => x.toJson())),
"Pause": List<dynamic>.from(pause.map((x) => x.toJson())),
};
}
class Exercise {
Exercise({
this.index,
this.name,
this.goal,
this.repGoal,
this.weightGoal,
this.timeGoal,
this.setGoal,
});
int index;
String name;
int goal;
int repGoal;
List<int> weightGoal;
int timeGoal;
List<String> setGoal;
Exercise.fromJson(dynamic json) {
// anything that is wrapped around with this [] in json is converted as list
// anything that is wrapped around with this {} is map
index = json["index"];
name = json["name"];
goal = json["goal"];
repGoal = json["repGoal"];
weightGoal = json["weightGoal"] != null ? json["weightGoal"].cast<int>() : [];
timeGoal = json["timeGoal"];
setGoal = json["setGoal"] != null ? json["setGoal"].cast<String>() : [];
}
Map<String, dynamic> toJson() => {
"index": index,
"name": name,
"goal": goal,
"repGoal": repGoal,
"weightGoal": List<dynamic>.from(weightGoal.map((x) => x)),
"timeGoal": timeGoal,
"setGoal": List<dynamic>.from(setGoal.map((x) => x)),
};
}
class Pause {
Pause({
this.index,
this.timeInMilSec,
});
int index;
int timeInMilSec;
factory Pause.fromJson(Map<String, dynamic> json) => Pause(
index: json["index"],
timeInMilSec: json["timeInMilSec"],
);
Map<String, dynamic> toJson() => {
"index": index,
"timeInMilSec": timeInMilSec,
};
}
Read this first.
You need to tweek this code a little to work for you but the idea is that;
also read comment in the code.
if json string comes with [] those around, json.decode will decode it as List<Map>.
if it comes with {} this json.decode will decode it as Map.
note: be careful while using generics on json.decode I reccommend not to.
data inside the jsonString does not really corresponds with the values inside the fromJson function. json string which you have provided was not really good. so I think you will understand how to manipulate it for your needs.
also main constructor Exercise you can use for initial data.
import 'dart:convert';
class Exercise{
Exercise({this.index,
this.name,
this.repGoal,
this.weightGoal,
this.setGoal});
String index;
String name;
String repGoal;
String weightGoal;
String setGoal;
Exercise.fromJson(dynamic json) :
// anything that is wrapped around with this [] in json is converted as list
// anything that is wrapped around with this {} is map
index = json["exercise"][0]["index"].toString(),
name = json["name"].toString(),
repGoal = json["repGoal"].toString(),
weightGoal = json["weightGoal"].toString(),
setGoal = json["setGoal"].toString();
}
void main(){
String jsonString = '{name: number 1, id: 56, exercise: [{"index":1,"weightGoal":[15,16,17], pause: [{"index":2}]}';
Map json = json.decode(jsonString);
Exercise.fromJson(json);
}
I found it out :)
I have restructured my fromJson to this, especially the jsonDecode was important, because json["exercise "] was only a String.
PlanModel.fromJson(dynamic json) {
name = json["name"];
if (json["exercise"] != null) {
exercise = [];
jsonDecode(json["exercise"]).forEach((v) {
exercise.add(Exercise.fromJson(v));
});
}}
now I can access it with
PlanModel item = snapshot.data[index];
item.exercise[0].timeGoal.toString()

How to convert data from json to List<Object> in Flutter

I need to obtain a list of Articles(a custom object) from a realtime database in Firebase. I first decode my data from a json data type. Then I try to convert it into a list using this line of code:
List<Article> articles = List<Article>.from(articleResponse)
.map((Map model) => Article.fromJson(model))
.toList();
However, this gives a syntax error of "The argument type 'Article Function(Map<dynamic,dynamic>)' can't be assigned to the parameter type 'dynamic Function(Article)'." I have included the code I use to fetch an Article(the custom object) as well as the factory method for the class.
//Method to get articles
Future<List<Article>> fetchArticles() async {
final response = await http.get(
"https://some-server.firebaseio.com/some-url.json");
final articleResponse = json.decode(response.body);
List<Article> articles = List<Article>.from(articleResponse)
.map((Map model) => Article.fromJson(model))
.toList(); // Now we're looping over the response entries (maps of article info) to create Article instances
return articles;
}
\\Factory Method
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
id: json['id'],
title: json['title'],
author: json['author'],
date: json['date'],
imageUrl: json['imageUrl'],
modalities: json['modalities'],
);
}
I make an example with something like a json response.
void main() {
//this is an example like a json response
List<Map<String, dynamic>> articleResponse = [
{
"id":"1",
"name":"test1"
},
{
"id":"2",
"name":"test2"
}
];
List<Article> articles = List<Article>.from(articleResponse.map((Map art)=>Article.fromJson(art)))
.toList();
print('${articles.length} articles in the list!! use to render de ui list');
}
class Article{
String id;
String name;
Article({this.id,this.name});
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
id: json['id'],
name: json['name'],
);
}
}
basically you need to change your method to get articles with this.
//Method to get articles
Future<List<Article>> fetchArticles() async {
final response = await http.get(
"https://some-server.firebaseio.com/some-url.json");
final articleResponse = json.decode(response.body);
List<Article> articles = List<Article>.from(articleResponse.map((Map art)=>Article.fromJson(art)))
.toList(); // Now we're looping over the response entries (maps of article info) to create Article instances
return articles;
}
you can use JsonToDart
this is create a class for parse your complex json data
paste json and get class of model
you can overrride toString in your model like:
#override
String toString() {
return '{
id: $id,
title: $title,
author: $author,
date: $date,
imageUrl: $imageUrl,
modalities: $modalities
}';
}
and override toMap :
Map<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'title': title,
'author': author,
'date': date,
'imageUrl': imageUrl,
'modalities': modalities,
};
}
and you can use serialization that. this can help you