I am setting up my model classes to confirm to the docs for sqflite which suggest including a named constructor to convert to/from Maps to better handling of data between the classes and the DB. Every example I can find is very simple, with class properties all being simple data types.
Using the constructor and method shown below, converting to/from Map is quite simple when dealing with a class such as this.
class Human{
final String name;
final String height;
Final String weight;
Human({this.name, this.height, this.weight});
}
However, when you have a class where one of the fields is a bit more complex, I do not understand how to structure things within the named constructor and xxx method to return the map of data that I 'believe' I should get.
class Human{
final String name;
final String height;
Final String weight;
List<Child> children = [];
Human({this.name, this.height, this.weight, this.children});
}
Human({this.name, this.height, this.weight, this.children});
Human.fromMap(Map<String, dynamic> map)
: name = map['name'],
height = map['height'],
weight = map['weight'],
children = map['children'];
Map<String, dynamic> toMap() {
return {
'name': name,
'height': height,
'weight': weight,
'children': children,
};
}
The List children is the part I am struggling with. I believe you have to get each Child object ALSO converted to a map within the parent map, but am losing the battle here.
Is my approach way off here? Is there some other method I should be using to accomplish this?
Any assistance would be much appreciated.
Here I am explaining the following
How to convert a model object into Map to use with sqlite
How to convert a Map object from sqlite into a model class.
How to parse JSON reponse properly in flutter
How to convert a model object into JSON
All of the above questions has same answer. Dart has great support for these operations. Here I am going to illustrate it with a detailed example.
class DoctorList{
final List<Doctor> doctorList;
DoctorList({this.doctorList});
factory DoctorList.fromMap(Map<String, dynamic> json) {
return DoctorList(
doctorList: json['doctorList'] != null
? (json['doctorList'] as List).map((i) => Doctor.fromJson(i)).toList()
: null,
);
}
Map<String, dynamic> toMap() {
final Map<String, dynamic> data = Map<String, dynamic>();
if (this.doctorList != null) {
data['doctorList'] = this.doctorList.map((v) => v.toMap()).toList();
}
return data;
}
}
The above DoctorList class has a member which holds a list of 'Doctor' objects..
And see how I parsed the doctorList.
doctorList: json['doctorList'] != null
? (json['doctorList'] as List).map((i) => Doctor.fromMap(i)).toList()
: null,
You may wonder, how the Doctor class may look like. Here you go
class Doctor {
final String doCode;
final String doctorName;
Doctor({this.doCode, this.doctorName});
factory Doctor.fromMap(Map<String, dynamic> json) {
return Doctor(
doCode: json['doCode'],
doctorName: json['doctorName'],
);
}
Map<String, dynamic> toMap() {
final Map<String, dynamic> data = Map<String, dynamic>();
data['doCode'] = this.doCode;
data['doctorName'] = this.doctorName;
return data;
}
}
That's all. Hope you got the idea. Cheers!
Related
I have this class to deserialize a paginated response from a server:
class PaginatedResponse {
final int current_page;
final dynamic data;
final String first_page_url;
final int from;
final int last_page;
final String last_page_url;
final String next_page_url;
final String path;
final int per_page;
final String prev_page_url;
final int to;
final int total;
PaginatedResponse({
this.current_page,
this.data,
this.first_page_url,
this.from,
this.last_page,
this.last_page_url,
this.next_page_url,
this.path,
this.per_page,
this.prev_page_url,
this.to,
this.total,
});
factory PaginatedResponse.fromJson(Map<String, dynamic> json) {
return json == null
? null
: PaginatedResponse(
current_page: json['current_page'],
data: json['data'],
first_page_url: json['first_page_url'],
from: json['from'],
last_page: json['last_page'],
last_page_url: json['last_page_url'],
next_page_url: json['next_page_url'],
path: json['path'],
per_page: json['per_page'],
prev_page_url: json['prev_page_url'],
to: json['to'],
total: json['total'],
);
}
Map<String, dynamic> toJson() => {
'current_page': current_page,
'data': data,
'first_page_url': first_page_url,
'from': from,
'last_page': last_page,
'last_page_url': last_page_url,
'next_page_url': next_page_url,
'path': path,
'per_page': per_page,
'prev_page_url': prev_page_url,
'to': to,
'total': total,
};
}
That same response is used for multiple different methods that expect different Types on the data property.
It would be ideal if I don't have to repeat all the other properties just to change the data type.
So how would I go about extending this class to allow for subclasses of this to have their proper Type in data?
Thank you!
To achieve that, you need to make PaginatedResponse with generic type of data. Like this:
class PaginatedResponse<DATA> {
final int current_page;
final DATA data;
...
The most difficult part is serialization/deserialization support of this by JSON frameworks. To me the best support of generics is implemented in built_value package. See here for complete example
I use https://pub.dev/packages/json_serializable to generate Json serialization for my classes. This works fine. Now I would like to ignore a single field only for the json generation but not when reading a json e.g. the dateOfBirth in following example:
#JsonSerializable()
class Person {
final String firstName;
final String lastName;
final DateTime dateOfBirth; //<-- ignore this field for json serialization but not for deserialization
Person({this.firstName, this.lastName, this.dateOfBirth});
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
When I use JsonKey.ignore the field is ignored for toJson and fromJson.
Is there a JsonKey Annotation for this case that I am missing?
Here's a workaround I've been using so I don't end up storing documentID's twice in my FB database while still having them available on the objects:
#JsonSerializable()
class Exercise {
const Exercise({
#required this.documentID,
// ...
}) : assert(documentID != null);
static toNull(_) => null;
#JsonKey(toJson: toNull, includeIfNull: false)
final String documentID;
//...
factory Exercise.fromJson(Map<String, dynamic> json) =>
_$ExerciseFromJson(json);
Map<String, dynamic> toJson() => _$ExerciseToJson(this);
}
where toNull is just
toNull(_) => null;
The toJson will null the value and then the includeIfNull won't serialize the value.
With null safety it can be:
#JsonKey(ignore: true)
final String documentID;
I know you can parse a json string then check if a value is of specific type. I wonder is there is a convention in Flutter to do that?
Example:
"{"id":1,"some_key":100}"
might be also
"{"id":1,"some_key":"GOOD"}"
Right now my objects is:
class someClass {
int id,
int some_key
}
I will have to change the some key to a String type I guess and then in the parsing check if the type is not string to convert the some_key to string? or is there a way do that?
Just make that variable dynamic in the class for parsing whatever data you recieve and when you are going to use it. You can just check it by using
someKey.runtimeType
to check the type and use by whatever way you want.
Your class will look something like this
class SomeClass {
int id;
dynamic someKey;
SomeClass({this.id, this.someKey});
SomeClass.fromJson(Map<String, dynamic> json) {
id = json['id'];
someKey = json['some_key'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['some_key'] = this.someKey;
return data;
}
}
and where you want to parse the data use this
SomeClass someclass = SomeClass.fromJson(jsonDecode(<YOUR JSON STRING HERE>));
I am trying to convert a list of objects(List<Object> in Flutter) to a defined type(class) in a flutter project. I have tried examples on converting the list as shown in Serializing Your Object in Flutter . Every time I get Map<String, dynamic> is not a subtype of List<dynamic>. But tried something different, same error. Posted is the sample code for the latter not from Serializing Your Object in Flutter. Is there an alternative to achieving this? Really appreciate your responses. [Updated]
Custom Object
#JsonSerializable(explicitToJson: true)
class Activity{
String id;
String name;
double amount;
Activity({ this.amount, this.name , this.id});
Activity.fromJson(Map<String, dynamic> json){
id = json["id"];
name = json["name"];
amount = json["amount"];
}
Map<String, dynamic> toJson() => {
'id' : id,
'name' : name,
'amount' : amount,
};
}
Sample Flutter Screen
initiateConnection(){
var builder = HubConnectionBuilder();
this._hubConnection = builder.withUrl("##########################").build();
_hubConnection.onclose( (error) => print("Connection Closed"));
this._hubConnection.on("Activities", formActivityDashboard );
this._hubConnection.start().then((data) => print("Connected"));
}
formActivityDashboard(List<Object> data){
final items = (data).map((i) => new Activity.fromJson(i));
for (final item in items) {
print(item.id);
}
}
List<dynamic>ActivityList=json.decode(response.body);
List<Activity> myActivityList = new List();
for(int i=0;i<ActivityList.length;i++)
{
Map<String, dynamic>ActivityData = ActivityList.elementAt(i);
Activity act=new Activity();
act.id=ActivityData['id']
act.name=ActivityData['name']
act.amount=ActivityData['amount']
myActivityList.add(act);
}
I have a simple Message document in my Firestore database that has some fields.
I use json_serializable to deserialize it to object. My class looks like follows:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'message_firestore.g.dart';
#JsonSerializable(nullable: true, explicitToJson: true)
class MessageFirestore extends Equatable {
MessageFirestore(
this.id, this.content, this.conversationId, this.senderId, this.dateSent);
factory MessageFirestore.fromJson(Map<String, dynamic> json) =>
_$MessageFirestoreFromJson(json);
Map<String, dynamic> toJson() => _$MessageFirestoreToJson(this);
#JsonKey(name: 'Id')
final String id;
#JsonKey(name: 'Content')
final String content;
#JsonKey(name: 'ConversationId')
final String conversationId;
#JsonKey(name: 'SenderId')
final String senderId;
#JsonKey(name: 'DateSent', fromJson: _fromJson, toJson: _toJson)
final DateTime dateSent;
static DateTime _fromJson(Timestamp val) =>
DateTime.fromMillisecondsSinceEpoch(val.millisecondsSinceEpoch);
static Timestamp _toJson(DateTime time) =>
Timestamp.fromMillisecondsSinceEpoch(time.millisecondsSinceEpoch);
}
There is no field called Id in the document, so currently its id is not being deserialized.
However, the key of the map retrieved from Firestore is its id, so this value can be read by manually deserializing the map.
I wish to have access to the id of the document (_b03002...) during deserialization.
Is there any way to configure json_serializable to read this id and store it in id property?
You could modify the fromJson constructor, so that you'd provide the id on the first parameter.
factory MessageFirestore.fromJson(String id, Map<String, dynamic> json) {
return _$MessageFirestoreFromJson(json)..id = id;
}
Then, from your caller, it'll be something like this
Message(snapshot.documentID, snapshot.data)
You could add another factory into MessageFirestore class.
factory MessageFirestore.fromFire(DocumentSnapshot doc) =>
_$MessageFirestoreFromFire(doc);
after that you will have two factory function in you class.
factory MessageFirestore.fromFire(DocumentSnapshot doc) //...
factory MessageFirestore.fromJson(Map<String, dynamic> json) //...
and add _$MessageFirestoreFromFire(doc) function with copying _$MessageFirestoreFromJson(json) function into message_firestore.g.dart file and edit it like this:
MessageFirestore _$MessageFirestoreFromFire(DocumentSnapshot doc) {
return MessageFirestore(
id: doc.documentID,
content: doc.data['name'] as String,
// ... other parameters
);
}
and in you service to reading the documents you can:
Stream<List<MessageFirestore>> getMessageList() {
return Firestore.instance
.collection('YourCollectionPath')
.snapshots()
.map((snapShot) => snapShot.documents
.map(
(document) => MessageFirestore.fromFire(document),
)
.toList());
}
Easy Peasy
And also this method doesn't interference with other classes that use MessageFirestore instance.
Have a nice day and wish this method works for you. :)
Improvement to Yaobin Then's post: Also remove id on toJson:
factory MessageFirestore.fromJson(String id, Map<String, dynamic> json) {
return _$MessageFirestoreFromJson(json)..id = id;
}
Map<String, dynamic> toJson() {
var json = _$MessageFirestoreToJson(this);
json.removeWhere((key, value) => key == 'id');
return json;
}
using Yaobin Then's answer, we can improve it forward like this:
factory MessageFirestore.fromJson(String id, Map<String, dynamic> json) {
return _$MessageFirestoreFromJson(json)..id = id;
}
factory MessageFirestore.fromFire(QueryDocumentSnapshot snapshot) {
return MessageFirestore.fromJson(snapshot.id, snapshot.data() as Map<String, dynamic>);
}
Then, from your caller, it'll be something like this
return StreamBuilder<QuerySnapshot>(
stream: ...,
builder: (context, snp) {
final products = snp.data?.docs;
if (products?.isNotEmpty != true) {
return const Center(
child: Text('No products'),
);
}
final prods = products!.map(
(prod) {
return MessageFirestore.fromFire(prod);
},
).toList();
this way you do not have to fill the 2 arguments in every call, the fromFire factory will handle it for you