如何使用json_serializable在Flutter中反序列化Firestore文档及其ID?

6

我在Firestore数据库中有一个简单的消息文档,它有一些字段。

enter image description here

我使用 json_serializable 将其反序列化为对象。我的类如下所示:

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

该文档中没有名为Id的字段,因此目前它的id没有被反序列化。

然而,从Firestore检索到的映射的key就是它的id,因此可以通过手动反序列化映射来读取这个值。

我希望在反序列化期间访问文档的id(_b03002...)。

有没有办法配置json_serializable以读取此id并将其存储在id属性中?


然而,从Firestore检索到的Map的关键是其id。你所说的“key”是什么?一个Map有很多keys。 - Renato
我所说的“地图的键”是屏幕截图上的b0300... ID。我发现它在DocumentSnapshot中作为属性documentID可用。我希望在反序列化对象时能够访问此ID。我将尝试在我的问题中澄清这一点。 - Dominik Roszkowski
4个回答

14
您可以修改fromJson构造函数,以便在第一个参数中提供ID。
factory MessageFirestore.fromJson(String id, Map<String, dynamic> json) {
  return _$MessageFirestoreFromJson(json)..id = id;
}

然后,从您的调用者那里,它将类似于这样

Message(snapshot.documentID, snapshot.data)

@YaobinThen 如果类是 Message 的属性,那么在 json_serializable 的上下文中是否可以实现这个? - flutter
toJson怎么办?需要以某种方式删除id,对吗? - Nicolas Degen
这取决于你是否想在你的JSON中包含id - Yaobin Then
你还需要将 final String id 改为 String? id 或至少是 late String id,然后从构造函数中删除它。否则,在从 Firebase 加载消息时可能会出现空错误。 - Neifen
从Freezed v2.2.0开始,当您在另一个模型类中使用freezed模型类作为字段时,此方法将不再起作用,并且您将从代码生成器中获得错误:期望具有恰好一个定位参数的`fromJson`构造函数。允许的唯一额外参数是形式为`T Function(Object?) fromJsonT`的函数,其中`T`是目标类型的类型参数。 - Maks
显示剩余5条评论

3
您可以在MessageFirestore类中添加另一个工厂。
  factory MessageFirestore.fromFire(DocumentSnapshot doc) =>
      _$MessageFirestoreFromFire(doc);

之后你的类中将有两个工厂函数。

factory MessageFirestore.fromFire(DocumentSnapshot doc) //...

factory MessageFirestore.fromJson(Map<String, dynamic> json) //...

并且在 message_firestore.g.dart 文件中添加 _$MessageFirestoreFromFire(doc) 函数,并将 _$MessageFirestoreFromJson(json) 函数复制到其中,然后按照以下方式进行编辑:

MessageFirestore _$MessageFirestoreFromFire(DocumentSnapshot doc) {
  return MessageFirestore(
    id: doc.documentID,
    content: doc.data['name'] as String,
    // ... other parameters 
  );
}

在阅读文档时,您可以:


  Stream<List<MessageFirestore>> getMessageList() {
    return Firestore.instance
        .collection('YourCollectionPath')
        .snapshots()
        .map((snapShot) => snapShot.documents
            .map(
              (document) => MessageFirestore.fromFire(document),
            )
            .toList());
  }

易如反掌

此方法也不会干扰使用MessageFirestore实例的其他类。

祝您拥有美好的一天,希望这种方法适用于您。 :)


2

改进Yaobin Then的帖子:同时在toJson上移除id:


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

0

使用Yaobin Then的答案,我们可以进一步改进它:

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

然后,从您的调用者来看,它将是这样的

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

这样你就不需要在每次调用时填写两个参数,fromFire工厂将为您处理。

在反序列化后设置id需要对象是可变的,这通常是不希望的。 - TomTom101
对于不可变对象,我们可以像这样做:factory MessageFirestore.fromJson(String id, Map<String, dynamic> json) { return MessageFirestoreFromJson(json).copyWith(id: id); } @TomTom101 - Adnan
然后 json_serializable 抱怨“期望恰好一个位置参数”或类似的错误。 - TomTom101
1
@TomTom101,你能试试这个吗?https://gist.github.com/adnanjpg/80cc0892b10bf1f1a14b53d20b8d6ad7? - Adnan
我想我应该转换到Freezed。这看起来很好,这里的一个小问题是对象生成了两次。 - TomTom101
好的,创建对象两次是我们必须付出的小代价。为了解决这个问题,我们要么自己编写JSON反序列化代码(这不是一个好主意),要么像我一样,在文档中只存储ID,这样当我获取数据时,快照数据()中已经有了ID。 - Adnan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接