Firestore Codable对象编码解码问题:如何保留DocumentId?
解决Firestore Codable对象存入UserDefaults时ID丢失的问题
问题根源
Firestore的@DocumentId属性是文档ID的映射字段,不属于文档的实际数据内容,所以用Firestore.encode编码成[String: Any]时会被自动忽略,导致存入UserDefaults后解码无法恢复ID。
更简洁的处理方法
方法一:自定义Codable实现,手动包含ID
跳过Firestore的默认编码,自己实现Encodable/Decodable,把@DocumentId标记的ID纳入编码流程:
struct User: Codable { @DocumentId var id: String? var name: String var email: String enum CodingKeys: String, CodingKey { case id, name, email } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) try container.encode(email, forKey: .email) } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(String.self, forKey: .id) name = try container.decode(String.self, forKey: .name) email = try container.decode(String.self, forKey: .email) } }
之后用标准JSONEncoder/JSONDecoder处理编码解码,直接存入UserDefaults:
// 编码存储 let user: User = ... // 从Firestore获取的带ID对象 do { let data = try JSONEncoder().encode(user) UserDefaults.standard.set(data, forKey: "currentUser") } catch { print("编码失败:\(error)") } // 解码读取 if let data = UserDefaults.standard.data(forKey: "currentUser") { do { let user = try JSONDecoder().decode(User.self, from: data) // ID会被正确保留 } catch { print("解码失败:\(error)") } }
方法二:编码后手动注入ID(轻量方案)
如果不想重写Codable逻辑,可以在Firestore.encode后手动把ID加入字典,解码时再提取赋值:
// 编码时注入ID let user: User = ... do { var userDict = try Firestore.encode(user) as [String: Any] userDict["id"] = user.id UserDefaults.standard.set(userDict, forKey: "currentUser") } catch { print("编码失败:\(error)") } // 解码时恢复ID if let userDict = UserDefaults.standard.dictionary(forKey: "currentUser") { do { var user = try Firestore.decode(User.self, from: userDict) user.id = userDict["id"] as? String // 使用对象 } catch { print("解码失败:\(error)") } }
这种方法比单独存ID更简洁,不用维护两个独立的存储项。
方法三:封装工具函数(进阶复用)
定义通用协议和工具函数,自动处理ID的编码解码:
// 定义协议统一处理ID protocol DocumentIdentifiable { var id: String? { get set } } // 编码工具:自动注入ID func encodeFirestoreObjectWithId<T: Codable & DocumentIdentifiable>(_ object: T) throws -> [String: Any] { var dict = try Firestore.encode(object) as [String: Any] dict["id"] = object.id return dict } // 解码工具:自动恢复ID func decodeFirestoreObjectWithId<T: Codable & DocumentIdentifiable>(_ type: T.Type, from dict: [String: Any]) throws -> T { var object = try Firestore.decode(type, from: dict) object.id = dict["id"] as? String return object } // 结构体遵守协议 struct User: Codable, DocumentIdentifiable { @DocumentId var id: String? var name: String var email: String }
调用时直接用工具函数,无需重复写ID处理逻辑:
// 编码存储 let user: User = ... let encodedDict = try encodeFirestoreObjectWithId(user) UserDefaults.standard.set(encodedDict, forKey: "currentUser") // 解码读取 if let dict = UserDefaults.standard.dictionary(forKey: "currentUser") { let user = try decodeFirestoreObjectWithId(User.self, from: dict) }
对你当前做法的评价
单独存储ID的方式是可行的,但需要维护两个独立的存储项,容易出现数据不一致的问题。上面的方法二或方法三更高效,能减少重复代码,也降低出错概率。
内容的提问来源于stack exchange,提问作者Patrick Meyer
相关产品推荐
相关产品推荐

