如何解析类型“时间戳”不是类型转换中“字符串”类型的子类型

Posted

技术标签:

【中文标题】如何解析类型“时间戳”不是类型转换中“字符串”类型的子类型【英文标题】:How do I resolve type 'Timestamp' is not a subtype of type 'String' in type cast 【发布时间】:2020-07-02 17:29:17 【问题描述】:

我想从 Firestore 获取会议并将它们映射到以下 Meeting 模型:

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);

从 Firestore 获取文档,然后在可迭代对象上调用 fromJson,但抛出异常:

type 'Timestamp' is not a subtype of type 'String' in type cast

当我进入生成的meeting.g.dart 时,正是这一行导致了错误

json['date'] == null ? null : DateTime.parse(json['date'] as String)

为了解决该问题,我尝试在模型中将 DateTime 更改为 Timestamp,但随后显示以下构建错误:

Error running JsonSerializableGenerator
Could not generate `fromJson` code for `date`.
None of the provided `TypeHelper` instances support the defined type.

你能告诉我你是如何解决这个问题的吗? 是否有另一种首选方法将 Firebase 和使用 json_serializable 进行 JSON 序列化的 Flutter 项目结合起来?甚至可以替换 json_serializable 的使用?

【问题讨论】:

你试过替换DateTime.parse(json['date'] as String)。与DateTime.parse(json['date'].toString()) 我认为编辑生成的文件不是一个好习惯。每次模型更改时我都必须这样做,因为会重新生成 meeting.g.dart。 你可能需要手动序列化 json,看看这篇帖子***.com/a/58309472/9609442 在生产中的应用程序中,我在上传之前将日期线解析为字符串,然后在获取时将其转换回日期时间。 你在 json['date'] 中得到了什么?是 millisecondsSinceEpoch 还是 dateString? 【参考方案1】:

使用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");

【讨论】:

工作就像一个魅力!谢谢。这也与freezed 兼容。上述其他解决方案使问题过于复杂。 @AlexHartford ? @ValentinSeehausen 您可以将 @TimestampConverter() 装饰器与冻结的类一起使用,就像使用 JsonSerializable 类一样。它的工作方式完全相同。 @AlexHartford 感谢您的回答。您是否使用当前的 nullsafe 版本对其进行了测试?不知何故,它对我不起作用。 @ValentinSeehausen 是的,您可以提出问题并发布您的代码吗?我很乐意看看。【参考方案2】:

感谢@Reed,指出正确的方向。当将DateTime 值传递给FireStore 时,firebase 将该值作为Timestamp 似乎没有问题,但是在取回它时需要正确处理。无论如何,这是一个例子,它可以双向工作:

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://***.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());
  

【讨论】:

【参考方案3】:

解决方案 #1

使用toJsonfromJson 转换器函数,如下例所示:https://github.com/dart-lang/json_serializable/blob/master/example/lib/example.dart

该解决方案的好处是您不必对属性名称进行硬编码

解决方案 #2

阅读https://github.com/dart-lang/json_serializable/issues/351 后,我更改了Meeting.fromJson,现在它可以正常工作了:

  factory Meeting.fromJson(Map<String, dynamic> json) 
    json["date"] = ((json["date"] as Timestamp).toDate().toString());
    return _$MeetingFromJson(json);
  

json["date"]默认是Timestamp,我在它到达生成的反序列化器之前将它转换为String,所以它在尝试转换json["date"] as String时不会崩溃

虽然,我不太喜欢这种解决方法,因为我必须硬编码属性的名称并耦合到类型,但现在,这种解决方案已经足够好了。

另一种方法是尝试使用https://pub.dev/packages/built_value 进行序列化,这是在他们的博客https://flutter.dev/docs/development/data-and-backend/json 中推荐的

【讨论】:

很高兴它有帮助。您是否尝试过此示例中的解决方案 github.com/dart-lang/json_serializable/blob/master/example/lib/… ? 不得不使用@JsonKey(fromJson: _dateTime, toJson: _dateTime) 来简单地让json_annotation 库忽略DateTime 按摩。这样,firestore 将正确处理该字段作为时间戳static DateTime _dateTime(DateTime dateTime) =&gt; dateTime; 您是如何在 dart 模型中定义日期/时间戳类型字段的?请建议。谢谢。 时间戳是 cloud_firestore 库中的一种类型【参考方案4】:

我遇到了同样的问题,但 json_serializer 仍然没有转换 Timestamp 对象,因为其中一些是 nullable

// nullable
class TimestampConverter implements JsonConverter<DateTime?, Timestamp?> 
  const TimestampConverter();

  @override
  DateTime? fromJson(Timestamp? timestamp) => timestamp?.toDate();

  @override
  Timestamp? toJson(DateTime? date) => date == null ? null : Timestamp.fromDate(date);

另外,我喜欢将额外的 id 字段放在流 method 中,这样它在课堂上看起来更干净。就我而言,我只是保存DocumentReference

Stream<User> streamUser() 
  return user().snapshots().map(
    (snapshot) 
      try 
        return User.fromDocument(snapshot);
       catch (e) 
        FirebaseWorker().signOut();
        rethrow;
      
    ,
  );

最后,我有多个 DateTime 属性,因此我可以将 @TimestampConverter 放在类的顶部。

您可以忽略属性,这样在使用@JsonKey(ignore: true) 调用toJson 时它不会被序列化。

所以最终代码如下所示:

@TimestampConverter() // <--
class User
  @JsonKey(ignore: true) // <--
  final String id;
  final DateTime? birthday;
  final DateTime timeCreated;

  User([this.id, this.timeCreated]);

  factory UserProfile.empty() => UserProfile(id: '', timeCreated: DateTime.now());

  factory UserProfile.fromJson(Map<String, dynamic> json) => _$UserProfileFromJson(json);

  factory UserProfile.fromDocument(DocumentSnapshot documentSnapshot) 
  
    final data = documentSnapshot.data();
    return data != null
        ? UserProfile.fromJson(data as Map<String, dynamic>)
            ..reference = documentSnapshot.reference
        : UserProfile.empty();

  Map<String, dynamic> toJson() => _$UserToJson(this); // <-- don't need to remove id anymore
  

【讨论】:

以上是关于如何解析类型“时间戳”不是类型转换中“字符串”类型的子类型的主要内容,如果未能解决你的问题,请参考以下文章

Hive/SparkSQL:如何将 Unix 时间戳转换为时间戳(不是字符串)?

java怎么判断是不是为时间戳

js 中日期 转换成时间戳 例如2013-08-30 转换为时间戳

时间戳以字符串类型(带有小数点)存储在hive里,现在要把时间戳字段转换成时间,要用啥函数,求转换!

时间戳如何储存数据

CDateTimeCtrl类型的日期如何比较大小