You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中使用JsonSerializable创建Firestore子集合的正确方法

在Flutter中结合JsonSerializable使用Firebase Firestore子集合

核心概念说明

Firestore的子集合是独立于父文档的集合,并非父文档的内嵌字段。所以你的AccountModel不需要直接包含子集合的列表数据,而是通过父文档的ID来定位对应的子集合。JsonSerializable仅负责处理文档本身的字段序列化,子集合的操作需要单独处理。

1. 创建子集合对应的模型

分别为用户和狗狗创建独立的模型类,同样使用JsonSerializable注解:

UserModel(用户子集合模型)

import 'package:json_annotation/json_annotation.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

part 'user_model.g.dart';

@JsonSerializable(explicitToJson: true)
class UserModel {
  String userId;
  String userName;
  int age;
  
  @JsonKey(fromJson: dateTimeFromTimestamp, toJson: dateTimeAsIs)
  DateTime? joinDate;

  UserModel({
    required this.userId,
    required this.userName,
    required this.age,
    this.joinDate,
  });

  factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
  Map<String, dynamic> toJson() => _$UserModelToJson(this);

  static DateTime? dateTimeFromTimestamp(Timestamp? timestamp) {
    return timestamp?.toDate();
  }

  static Timestamp? dateTimeAsIs(DateTime? dateTime) {
    return dateTime != null ? Timestamp.fromDate(dateTime) : null;
  }
}

DogModel(狗狗子集合模型)

import 'package:json_annotation/json_annotation.dart';

part 'dog_model.g.dart';

@JsonSerializable(explicitToJson: true)
class DogModel {
  String dogId;
  String breed;
  String name;
  int age;

  DogModel({
    required this.dogId,
    required this.breed,
    required this.name,
    required this.age,
  });

  factory DogModel.fromJson(Map<String, dynamic> json) => _$DogModelFromJson(json);
  Map<String, dynamic> toJson() => _$DogModelToJson(this);
}

2. 完善AccountModel

在AccountModel中添加获取子集合引用的方法(注意:该方法无需序列化,因为Firestore的CollectionReference无法被JsonSerializable处理),同时补全序列化逻辑:

import 'package:json_annotation/json_annotation.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'user_model.dart';
import 'dog_model.dart';

part 'account_model.g.dart';

@JsonSerializable(explicitToJson: true)
class AccountModel {
  String accountId;
  String accountName;
  
  @JsonKey(fromJson: dateTimeFromTimestamp, toJson: dateTimeAsIs)
  DateTime? createdAt;

  AccountModel({
    required this.accountId,
    required this.accountName,
    this.createdAt,
  });

  factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json);
  Map<String, dynamic> toJson() => _$AccountModelToJson(this);

  // 获取用户子集合引用
  CollectionReference<UserModel> getUsersCollection() {
    return FirebaseFirestore.instance
        .collection('accounts')
        .doc(accountId)
        .collection('users')
        .withConverter<UserModel>(
          fromFirestore: (snapshot, _) => UserModel.fromJson(snapshot.data()!),
          toFirestore: (user, _) => user.toJson(),
        );
  }

  // 获取狗狗子集合引用
  CollectionReference<DogModel> getDogsCollection() {
    return FirebaseFirestore.instance
        .collection('accounts')
        .doc(accountId)
        .collection('dogs')
        .withConverter<DogModel>(
          fromFirestore: (snapshot, _) => DogModel.fromJson(snapshot.data()!),
          toFirestore: (dog, _) => dog.toJson(),
        );
  }

  static DateTime? dateTimeFromTimestamp(Timestamp? timestamp) {
    return timestamp?.toDate();
  }

  static Timestamp? dateTimeAsIs(DateTime? dateTime) {
    return dateTime != null ? Timestamp.fromDate(dateTime) : null;
  }
}

3. 子集合读写示例

写入子集合数据

// 实例化账户模型
final account = AccountModel(
  accountId: 'account_123',
  accountName: 'My Family Account',
  createdAt: DateTime.now(),
);

// 写入用户到子集合
final user = UserModel(
  userId: 'user_456',
  userName: 'John Doe',
  age: 30,
  joinDate: DateTime.now(),
);
await account.getUsersCollection().doc(user.userId).set(user);

// 写入狗狗到子集合
final dog = DogModel(
  dogId: 'dog_789',
  breed: 'Golden Retriever',
  name: 'Buddy',
  age: 2,
);
await account.getDogsCollection().doc(dog.dogId).set(dog);

读取子集合数据

// 获取账户下所有用户
final usersSnapshot = await account.getUsersCollection().get();
final users = usersSnapshot.docs.map((doc) => doc.data()).toList();

// 获取账户下所有狗狗
final dogsSnapshot = await account.getDogsCollection().get();
final dogs = dogsSnapshot.docs.map((doc) => doc.data()).toList();

关键注意点

  • 子集合不会随父文档删除自动删除,需手动处理删除逻辑。
  • 使用withConverter可直接在集合引用中绑定模型,省去手动序列化/反序列化步骤。
  • 父模型的accountId必须准确,否则无法定位到对应子集合。

内容的提问来源于stack exchange,提问作者Almog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:30:54