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;
Related
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,
};
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.
Structure of My Dto is like -
#freezed
abstract class MessageDto with _$MessageDto{
factory MessageDto({
String message,
#JsonKey(name: 'message_type') String messageType,
#JsonKey(name: 'sender_id') String senderId,
#JsonKey(name: 'sent_at') Timestamp sendAt,
}) = _MessageDto;
factory MessageDto.fromFirestore(DocumentSnapshot doc) {
Map<String, dynamic> json = doc.data;
return MessageDto.fromJson(json);
}
factory MessageDto.fromJson(Map<String, dynamic> json) => _$MessageDtoFromJson(json);
}
The generator is not supporting TimeStamp data type.
When I am using DateTime dataType, it is generating code but throwing exception on parsing the documentSnaphot data as
Unhandled Exception: type 'Timestamp' is not a subtype of type 'String' in type cast
You can pass a custom fromJson and toJson function to the #JsonKey.
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/JsonKey.html
Timestamp _sendAtFromJson(Timestamp timestamp) => timestamp;
#freezed
abstract class MessageDto with _$MessageDto{
factory MessageDto({
String message,
#JsonKey(name: 'message_type') String messageType,
#JsonKey(name: 'sender_id') String senderId,
#JsonKey(name: 'sent_at', fromJson: _sendAtFromJson) Timestamp sendAt,
}) = _MessageDto;
factory MessageDto.fromFirestore(DocumentSnapshot doc) {
Map<String, dynamic> json = doc.data;
return MessageDto.fromJson(json);
}
factory MessageDto.fromJson(Map<String, dynamic> json) => _$MessageDtoFromJson(json);
}
Its better to convert the timestamp to datetime
// To parse this JSON data, do
//
// final notificationModel = notificationModelFromJson(jsonString);
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'notification_model.freezed.dart';
part 'notification_model.g.dart';
NotificationModel notificationModelFromJson(String str) =>
NotificationModel.fromJson(json.decode(str));
String notificationModelToJson(NotificationModel data) =>
json.encode(data.toJson());
#freezed
abstract class NotificationModel with _$NotificationModel {
const factory NotificationModel({
String? title,
String? uid,
String? description,
#JsonKey(name: 'createdOn', fromJson: _sendAtFromJson) DateTime? createdOn,
}) = _NotificationModel;
factory NotificationModel.fromJson(Map<String, dynamic> json) =>
_$NotificationModelFromJson(json);
}
DateTime _sendAtFromJson(Timestamp timestamp) =>
DateTime.fromMillisecondsSinceEpoch(timestamp.millisecondsSinceEpoch);
In my case only from json method was not enough .. I wanted to convert from / to json using millisecondsFromEpoch:
Timestamp _timestampFromJson(int timestamp) => Timestamp.fromMillisecondsSinceEpoch(timestamp);
int _timestampToJson(Timestamp timestamp) => timestamp.millisecondsSinceEpoch ;
and in your freezed field that has the type Timestamp :
#JsonKey(name: 'key_name', fromJson: _timestampFromJson , toJson: _timestampToJson)
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);
}
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