flutter dart: how to encode a map / dictionary to JSON - 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?

Related

Firebase nested HttpsCallableResult to dart model

I'm trying to get some data from a cloud function and assign it to a model. But I am unable to used the nested data, I get the following error:
_TypeError (type '_InternalLinkedHashMap<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>')
The data I receive looks like this:
{
"answer": "Some optional answer",
"error": "Some optional error",
"usage": { "prompt_tokens": 32, "completion_tokens": 40, "total_tokens": 72 }
}
When I receive the data I try to assign it to a model:
final HttpsCallableResult result = await functions
.httpsCallable('askHW')
.call({'question': userQuestion});
return HWResponse.fromJson(result.data);
HWResoponse:
class HWResponse {
final String answer;
final String error;
final HWUsage usage;
HWResponse({this.answer = '', this.error = '', required this.usage});
factory HWResponse.fromJson(Map<String, dynamic> json) => HWResponse(
answer: json.containsKey('answer') ? json['answer'] as String : '',
error: json.containsKey('error') ? json['error'] as String : '',
usage:
json["usage"] == null ? HWUsage() : HWUsage.fromJson(json["usage"]),
);
Map<String, dynamic> toJson() => {
"answer": answer,
"error": error,
"usage": usage.toJson(),
};
}
class HWUsage {
final int promptTokens;
final int completionTokens;
final int totalTokens;
HWUsage({
this.promptTokens = 0,
this.completionTokens = 0,
this.totalTokens = 0,
});
factory HWUsage.fromJson(Map<String, dynamic> json) => HWUsage(
promptTokens: json.containsKey('prompt_tokens')
? json['prompt_tokens'] as int
: 0,
completionTokens: json.containsKey('completion_tokens')
? json['completion_tokens'] as int
: 0,
totalTokens:
json.containsKey('total_tokens') ? json['total_tokens'] as int : 0,
);
Map<String, dynamic> toJson() => {
"prompt_tokens": promptTokens,
"completion_tokens": completionTokens,
"total_tokens": totalTokens,
};
}
Using Map<String, dynamic>.from('json['usage]') in HWResponse on the nested field seems to be the correct way to do this. To avoid any errors I also used json.containsKey('usage') to make sure that the nested field usage actually exists.
usage: json.containsKey('usage')
? HWUsage.fromJson(Map<String, dynamic>.from(json['usage']))
: HWUsage(),

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 parse dynamic JSON keys in dart

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.

How to convert List<CustomModel> to Map<String,dynamic> in Flutter

I'm using HydratedBloc in Flutter. In order to work with HydratedBloc, I need to convert my custom List(parsed from JSON string) List<List<Category>> to Map<String, dynamic>. Here is the model that I'm using to parse my json string
import 'dart:convert';
List<List<Category>> rpTrendingCategoriesFromJson(String str) => List<List<Category>>.from(json.decode(str).map((x) => List<Category>.from(x.map((x) => Category.fromJson(x)))));
String rpTrendingCategoriesToJson(List<List<Category>> data) => json.encode(List<dynamic>.from(data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))));
class Category {
Category({
this.title,
this.imageLink,
});
String title;
String imageLink;
factory Category.fromJson(Map<String, dynamic> json) => Category(
title: json["title"] == null ? null : json["title"],
imageLink: json["imageLink"] == null ? null : json["imageLink"],
);
Map<String, dynamic> toJson() => {
"title": title == null ? null : title,
"imageLink": imageLink == null ? null : imageLink,
};
}
And here I need to convert that List<List<Category>> to Map<String, dynamic>
class TrendingCategoriesCubit extends Cubit<TrendingCategoriesState>
with HydratedMixin {
Repository repository;
TrendingCategoriesCubit({#required this.repository})
: super(TrendingCategoriesLoading());
Future<void> fetchTrendingCategories() async {
emit(TrendingCategoriesLoading());
List<List<Category>> categories =
await repository.fetchTrendingCategories();
if (categories.isEmpty) {
emit(TrendingCategoriesFetchedEmpty());
} else if (categories.isNotEmpty) {
emit(TrendingCategoriesFetched(categories: categories));
}
}
#override
TrendingCategoriesState fromJson(Map<String, dynamic> json) {
try {
final categories = rpTrendingCategoriesFromJson(json.toString());
return TrendingCategoriesFetched(categories: categories);
} catch (_) {
return null;
}
}
#override
Map<String, dynamic> toJson(TrendingCategoriesState state) {
if (state is TrendingCategoriesFetched) {
return state.categories.toJson(); //coudn't do it
} else {
return null;
}
}
}
If you look at the toJson() method above, I need to return List<List<Categor>> to Map<String,dynamic. How can I do it?
A simple workaround to achieve the desired result would be to just create a Map for the toJson/fromJson part of the Hydrated BLoC:
#override
TrendingCategoriesState fromJson(Map<String, dynamic> json) {
try {
final categoriesJson = json['categories'];
final categories = rpTrendingCategoriesFromJson(json.toString());
return TrendingCategoriesFetched(categories: categories);
} catch (_) {
return null;
}
}
#override
Map<String, dynamic> toJson(TrendingCategoriesState state) {
if (state is TrendingCategoriesFetched) {
return <String, dynamic>{
'categories': state.categories.toJson(),
};
} else {
return null;
}
}
Just adjust the code a little bit for it to compile, but the general idea is to create a "temporary" Map object only to persist and restore the hydrated state.

How to define a model with a property that can be both null or another model in Dart

I am building a Flutter app and have some trouble defining a model. I have model which has some properties. One of those properties can be both null or another model.
import 'package:proef/models/worked_time.dart';
class Shift {
String uuid;
DateTime start_at;
DateTime end_at;
String title;
String comment;
WorkedTime worked_time;
Shift(
{this.uuid,
this.start_at,
this.end_at,
this.title,
this.comment,
this.worked_time});
factory Shift.fromData(Map<String, dynamic> parsedJson) {
print(parsedJson);
return Shift(
uuid: parsedJson['uuid'],
start_at: DateTime.parse(parsedJson['start_at']).toLocal(),
end_at: DateTime.parse(parsedJson['end_at']).toLocal(),
title: parsedJson['title'],
comment: parsedJson['comment'],
worked_time: parsedJson['worked_time'] == null
? null
: parsedJson['worked_time']);
}
}
This does not work. When I use this model it throws me the following error:
Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'WorkedTime'
I am not sure how to fix this, since I am quite new to Dart and Flutter.
The WorkedTime model:
class WorkedTime {
String uuid;
String comment;
int status;
int took_break;
DateTime start_at;
DateTime end_at;
WorkedTime({
this.uuid,
this.comment,
this.status,
this.took_break,
this.start_at,
this.end_at,
});
factory WorkedTime.fromData(Map<String, dynamic> parsedJson) {
print(parsedJson);
return WorkedTime(
uuid: parsedJson['uuid'],
comment: parsedJson['comment'],
status: parsedJson['status'],
took_break: parsedJson['took_break'],
start_at: DateTime.parse(parsedJson['start_at']),
end_at: DateTime.parse(parsedJson['end_at']));
}
}
The JSON with worked_time
[
{
"uuid": "706f40e7-d57c-470c-9023-b0c58e2c7c3a",
"start_at": "2020-09-01T08:00:00.000000Z",
"end_at": "2020-09-01T16:00:00.000000Z",
"title": "Test",
"comment": "Test",
"worked_time": {
"uuid": "6e73b4aa-d6e1-41f7-86cb-09745d2db033",
"comment": "Test",
"status": 0,
"break": 0,
"start_at": "2020-09-01T08:00:00.000000Z",
"end_at": "2020-09-01T16:00:00.000000Z",
"took_break": 0
}
}
]
The JSON without worked_time
[
{
"uuid": "706f40e7-d57c-470c-9023-b0c58e2c7c3a",
"start_at": "2020-09-01T08:00:00.000000Z",
"end_at": "2020-09-01T16:00:00.000000Z",
"title": "Test",
"comment": "Test",
"worked_time": null
}
]
Can you try this model
// To parse this JSON data, do
//
// final shift = shiftFromJson(jsonString);
import 'dart:convert';
List<Shift> shiftFromJson(String str) => List<Shift>.from(json.decode(str).map((x) => Shift.fromJson(x)));
String shiftToJson(List<Shift> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Shift {
Shift({
this.uuid,
this.startAt,
this.endAt,
this.title,
this.comment,
this.workedTime,
});
String uuid;
DateTime startAt;
DateTime endAt;
String title;
String comment;
WorkedTime workedTime;
factory Shift.fromJson(Map<String, dynamic> json) => Shift(
uuid: json["uuid"] == null ? null : json["uuid"],
startAt: json["start_at"] == null ? null : DateTime.parse(json["start_at"]),
endAt: json["end_at"] == null ? null : DateTime.parse(json["end_at"]),
title: json["title"] == null ? null : json["title"],
comment: json["comment"] == null ? null : json["comment"],
workedTime: json["worked_time"] == null ? null : WorkedTime.fromJson(json["worked_time"]),
);
Map<String, dynamic> toJson() => {
"uuid": uuid == null ? null : uuid,
"start_at": startAt == null ? null : startAt.toIso8601String(),
"end_at": endAt == null ? null : endAt.toIso8601String(),
"title": title == null ? null : title,
"comment": comment == null ? null : comment,
"worked_time": workedTime == null ? null : workedTime.toJson(),
};
}
class WorkedTime {
WorkedTime({
this.uuid,
this.comment,
this.status,
this.workedTimeBreak,
this.startAt,
this.endAt,
this.tookBreak,
});
String uuid;
String comment;
int status;
int workedTimeBreak;
DateTime startAt;
DateTime endAt;
int tookBreak;
factory WorkedTime.fromJson(Map<String, dynamic> json) => WorkedTime(
uuid: json["uuid"] == null ? null : json["uuid"],
comment: json["comment"] == null ? null : json["comment"],
status: json["status"] == null ? null : json["status"],
workedTimeBreak: json["break"] == null ? null : json["break"],
startAt: json["start_at"] == null ? null : DateTime.parse(json["start_at"]),
endAt: json["end_at"] == null ? null : DateTime.parse(json["end_at"]),
tookBreak: json["took_break"] == null ? null : json["took_break"],
);
Map<String, dynamic> toJson() => {
"uuid": uuid == null ? null : uuid,
"comment": comment == null ? null : comment,
"status": status == null ? null : status,
"break": workedTimeBreak == null ? null : workedTimeBreak,
"start_at": startAt == null ? null : startAt.toIso8601String(),
"end_at": endAt == null ? null : endAt.toIso8601String(),
"took_break": tookBreak == null ? null : tookBreak,
};
}
The compiler does not complain about assigning a dynamic variable to a typed variable because the check is done at runtime. Therefore, you can write:
void main() {
dynamic value = "something";
String string = value; // ok, value holds a String; runtime is fine
WorkedTime time = value; // evaluated at runtime; exception
}
This issue is present in Shift's factory constructor:
worked_time: parsedJson['worked_time'] == null
? null // true: can assign null to WorkedTime
: parsedJson['worked_time']); // false: assign dynamic to WorkedTime
You are attempting to assign a dynamic type with a value of parsedJson['worked_time'] to your WorkedTime type. These are incompatible types. Instead, you must use WorkedTime's constructor to construct the instance from the JSON map. Here is a minified example of your issue:
class WorkedTime {
String comment;
WorkedTime.fromData(Map<String, dynamic> json) : comment = json['comment'];
}
class Shift {
String uuid;
WorkedTime worked_time;
Shift.fromData(Map<String, dynamic> json)
: uuid = json['uuid'],
// next line causes the exception; cannot assign dynamic to WorkedTime
// fix: use WorkedTime.fromData(json['worked_time'])
worked_time = json['worked_time'];
}
In order to fix this, use WorkedTime.fromData(...)'s constructor.
class Shift {
String uuid;
WorkedTime worked_time;
Shift.fromData(Map<String, dynamic> json)
: uuid = json['uuid'],
worked_time = json['worked_time'] != null
? WorkedTime.fromData(json['worked_time']) // parse WorkedTime's json
: null;
}
void main() {
const json = <String, dynamic>{
'uuid': '706f40e7-d57c-470c-9023-b0c58e2c7c3a',
'worked_time': <String, dynamic>{'comment': 'on break'}
};
final shift = Shift.fromData(json);
// check that shift.worked_time is not null, etc.
print(shift.worked_time.comment);
}