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