Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I resolve type 'Timestamp' is not a subtype of type 'String' in type cast

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 ?

like image 484
Reed Avatar asked Mar 21 '20 21:03

Reed


3 Answers

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");
}
like image 100
Junsu Lee Avatar answered Nov 04 '22 12:11

Junsu Lee


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());
  }
}
like image 25
mike123 Avatar answered Nov 04 '22 12:11

mike123


Solution #1

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

Solution #2

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

like image 31
Reed Avatar answered Nov 04 '22 13:11

Reed