Do not parse response when data is empty for a key - flutter

I have a model like this, in this scenario in failure case my data can be empty and there will be an error in response. Something like this. With this in API failure case as Message model has keys which have non-null value the parsing is failing as data key is empty. How we can handle this case?
{
"data": {
},
"success": false,
"errors": [
{
}
]
}
#JsonSerializable()
class MessageResponse {
factory MessageResponse.fromJson(Map<String, dynamic> json) =>
_$MessageResponseFromJson(json);
Map<String, dynamic> toJson() => _$MessageResponseToJson(this);
#JsonKey(name: "data")
late Message data;
#JsonKey(name: "errors")
late List<Map<String, dynamic>> errors;
MessageResponse(
this.data,
this.success,
this.errors,
);
}
class Message {
factory Message.fromJson(Map<String, dynamic> json) =>
_$MessageFromJson(json);
Map<String, dynamic> toJson() => _$MessageToJson(this);
#JsonKey(name: "id")
late String id;
}

As per this (closed) issue, there is no way to achieve this.
If you have control over the response (the backend), try returning null when you have error(s) and use #JsonKey(includeIfNull: true) for the data key.
Otherwise, you can write your own class extending the JsonConverter class. Refer this example.

Related

flutter map was called on a null value

I try to get data from api. CallListDto class is empty according to user type. So, sometime this class is empty sometimes it has data. I populate dropdown menu items with this class.
My problem is, when this class is empty i got error. How can i solve this.
My model class is below
Login loginFromJson(String str) => Login.fromJson(json.decode(str));
String loginToJson(Login data) => json.encode(data.toJson());
class Login {
Login({
required this.token,
required this.callListDto,
});
String token;
List<CallListDto> callListDto;
factory Login.fromJson(Map<String, dynamic> json) => Login(
token: json["token"],
callListDto: List<CallListDto>.from(
json["callListDto"].map((x) => CallListDto.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"token": token,
"callListDto": List<dynamic>.from(callListDto.map((x) => x.toJson())),
};
}
class CallListDto {
CallListDto({
required this.callId,
required this.stationCode,
required this.callType,
});
int callId;
String stationCode;
int callType;
factory CallListDto.fromJson(Map<String, dynamic> json) => CallListDto(
callId: json["callID"],
stationCode: json["stationCode"],
callType: json["callType"],
);
Map<String, dynamic> toJson() => {
"callID": callId,
"stationCode": stationCode,
"callType": callType,
};
}
Before calling the model class check that str is not empty, for example
if (dataObject.isEmpty) return;

How to do a toJson encode method on Dart/Flutter?

I used the fromJson method to recover a Struct with a List from Json decode http request and receiver it on my class, but now i want to do a reverse, i want to pass the data on my class to my toJson method and send him to a Json encode http POST. Please, i new on Dart/Flutter, someone know how to do this?
import 'dart:convert';
List<Itens> userFromJson(String str) =>
List<Itens>.from(jsonDecode(str).map((x) => Itens.fromJson(x)));
class Coletas {
final int codigo;
final String dataIni;
late String? dataFin;
late String? status;
final List<Itens> itemList;
Coletas(
{
required this.dataIni,
this.dataFin,
this.status,
required this.codigo,
required this.itemList
}
);
factory Coletas.fromJson(Map<String, dynamic> json) {
return Coletas(
dataIni: json['dtData'],
codigo: json['iCodigo'],
itemList: List<Itens>.from(json['stItens'].map((x) => Itens.fromJson(x))),
);
}
Map<String, dynamic> toMap() {
return {
'codigo': codigo,
'dataIni': dataIni,
'dataFin': dataFin,
'status': status
};
}
}
class Itens {
final int? id;
final int codigo;
late int quantidade;
late String? status;
final String codigoEAN;
Itens({
this.id,
this.status,
required this.codigo,
required this.codigoEAN,
required this.quantidade,
});
Map<String, dynamic> toJson(){
return {
'icodigo' : codigo,
'sCodigoBarras': codigoEAN,
'iQtd': quantidade
};
}
factory Itens.fromJson(Map<String, dynamic> json) {
return Itens(
codigo: json['iCodigo'],
codigoEAN: json['sCodigoBarras'],
quantidade: json['iQtd'],
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'status': status,
'codigo': codigo,
'codigoEAN': codigoEAN,
'quantidade': quantidade,
};
}
}
I tried to pass ever item on List separeted so, but not happen i expected.
Map<String, dynamic> toJSon(Coletas value) =>
{
'dtData' : dataIni,
'iCodigo': codigo,
'stItens': [],
};
For a better structure - format and use you can look at the flutter serialization documentation : https://docs.flutter.dev/development/data-and-backend/json.
It explains how to create your model and how to generate them to create fromJson and toJson Model based on the defined data. (https://docs.flutter.dev/development/data-and-backend/json#creating-model-classes-the-json_serializable-way)
It will helps you with your parsing - sending - receiving data.
I think you should assign Coletas as
Map<String, dynamic> toJSon(Coletas value) =>
{
'dtData' : value.dataIni,
'iCodigo': value.codigo,
'stItens': value.itemList,
};

flutter dart JsonSerializable with inherited class

I have the following two classes where one is extending from the other like this :
#JsonSerializable(nullable: true)
class Response {
final String responseCode;
final String responseMessage;
final String errorLog;
Response({this.errorLog, this.responseCode, this.responseMessage});
factory Response.fromJson(Map<String, dynamic> json) =>
_$ResponseFromJson(json);
}
.........................................................
#JsonSerializable(nullable: false)
class Verify extends Response {
Data data;
Verify({
this.data,
});
factory Verify.fromJson(Map<String, dynamic> json) => _$VerifyFromJson(json);
Map<String, dynamic> toJson() => _$VerifyToJson(this);
}
and whenever I'm trying to read response class properties from Verify class, it's always null.
so please how to achieve this?
this one I have solved by passing the parameters to super in verify class constructor like this
#JsonSerializable()
class VerifyResponse extends Response {
Data data;
VerifyResponse({
this.data,
String responseCode,
String responseMessage,
}) : super(responseCode: responseCode, responseMessage: responseMessage);
factory VerifyResponse.fromJson(Map<String, dynamic> json) =>
_$VerifyResponseFromJson(json);
Map<String, dynamic> toJson() => _$VerifyResponseToJson(this);
}
and for the response class it remains the same
#JsonSerializable()
class Response {
final String responseCode;
final String responseMessage;
Response({this.responseCode, this.responseMessage});
factory Response.fromJson(Map<String, dynamic> json) =>
_$ResponseFromJson(json);
}
it's a bit annoying but it's what it's.
You should remove 'final' keyword from Response Class
#JsonSerializable(nullable: true)
class Response {
String responseCode;
String responseMessage;
String errorLog;
Response({this.errorLog, this.responseCode, this.responseMessage});
factory Response.fromJson(Map<String, dynamic> json) =>
_$ResponseFromJson(json);
}
It worked by adding super(); explicitly to the child class's constructor.
#JsonSerializable()
class VerifyResponse extends Response {
Data data;
VerifyResponse({
this.data,
String responseCode,
String responseMessage,
//No need to list all parent class properties
}) : super();
factory VerifyResponse.fromJson(Map<String, dynamic> json) =>
_$VerifyResponseFromJson(json);
Map<String, dynamic> toJson() => _$VerifyResponseToJson(this);
}

How to exclude a single field from json serialization with json_serializable?

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;

How to deserialize Firestore document with its id using json_serializable in Flutter?

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