I want to fetch meetings from Firestore and map them into the following Meeting model:
part 'meeting.g.dart';
@JsonSerializable(explicitToJson: true)
class Meeting {
String id;
DateTime date;
Meeting(this.id, this.date);
factory Meeting.fromJson(Map<String, dynamic> json) {
return _$MeetingFromJson(json);
}
Map<String, dynamic> toJson() => _$MeetingToJson(this);
}
The documents are fetched from Firestore and then fromJson is called on the iterable, but an exception is thrown:
type 'Timestamp' is not a subtype of type 'String' in type cast
When I go into generated meeting.g.dart, it's this line which causes the error
json['date'] == null ? null : DateTime.parse(json['date'] as String)
To workaround the issue, I've tried changing from DateTime to Timestamp in the model, but then the following build error is shown:
Error running JsonSerializableGenerator
Could not generate `fromJson` code for `date`.
None of the provided `TypeHelper` instances support the defined type.
Could you tell me how do you solve this issue? Is there another preferred way to combine Firebase and a Flutter project using json_serializable for JSON serialization? Maybe even replace usage of json_serializable ?
Use JsonConverter
class TimestampConverter implements JsonConverter<DateTime, Timestamp> {
const TimestampConverter();
@override
DateTime fromJson(Timestamp timestamp) {
return timestamp.toDate();
}
@override
Timestamp toJson(DateTime date) => Timestamp.fromDate(date);
}
@JsonSerializable()
class User{
final String id;
@TimestampConverter()
final DateTime timeCreated;
User([this.id, this.timeCreated]);
factory User.fromSnapshot(DocumentSnapshot documentSnapshot) =>
_$UserFromJson(
documentSnapshot.data..["_id"] = documentSnapshot.documentID);
Map<String, dynamic> toJson() => _$UserToJson(this)..remove("_id");
}
Thanks to @Reed, for pointing to the right direction. When passing DateTime value to FireStore there seem no issues for firebase to take that value as Timestamp, however when getting it back it needs to be properly handled. Anyways, here is example, that works both ways:
import 'package:cloud_firestore/cloud_firestore.dart'; //<-- dependency referencing Timestamp
import 'package:json_annotation/json_annotation.dart';
part 'test_date.g.dart';
@JsonSerializable(anyMap: true)
class TestDate {
@JsonKey(fromJson: _dateTimeFromTimestamp, toJson: _dateTimeAsIs)
final DateTime theDate;
TestDate({this.theDate,});
factory TestDate.fromJson(Map<String, dynamic> json) {
return _$TestDateFromJson(json);
}
Map<String, dynamic> toJson() => _$TestDateToJson(this);
static DateTime _dateTimeAsIs(DateTime dateTime) => dateTime; //<-- pass through no need for generated code to perform any formatting
// https://stackoverflow.com/questions/56627888/how-to-print-firestore-timestamp-as-formatted-date-and-time-in-flutter
static DateTime _dateTimeFromTimestamp(Timestamp timestamp) {
return DateTime.parse(timestamp.toDate().toString());
}
}
Use toJson and fromJson converter functions as in the following example: https://github.com/dart-lang/json_serializable/blob/master/example/lib/example.dart
Benefits of the solution is that you don't have to hard-code property names
After reading https://github.com/dart-lang/json_serializable/issues/351, I've changed Meeting.fromJson and it works as expected now:
factory Meeting.fromJson(Map<String, dynamic> json) {
json["date"] = ((json["date"] as Timestamp).toDate().toString());
return _$MeetingFromJson(json);
}
json["date"] is Timestamp by default, I convert it to String, before it reaches the generated deserializer, so it doesn't crash when it tries to cast json["date"] as String
Though, I don't like this workaround very much, because I have to hard-code property's name and couple to types, but for now, this solution will be good enough.
An alternative would be to try out https://pub.dev/packages/built_value for serialiazion, which is recommended in their blog https://flutter.dev/docs/development/data-and-backend/json
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With