Swift使用Codable解码Firebase Timestamp报seconds键缺失解决方案咨询
错误原因
Swift端Firebase的Timestamp类型默认Codable实现会读取seconds、nanoseconds两个字段完成解码,而Firebase Functions返回的JSON中,Firestore Timestamp默认序列化后会生成带下划线前缀的_seconds、_nanoseconds字段,部分场景下也会直接序列化为ISO 8601格式的日期字符串,字段不匹配导致解码失败。
可行处理方案
以下方案无需引入第三方库,可按需选择:
方案1:全局扩展Timestamp实现自定义Codable逻辑
该方案修改全局的Timestamp解码规则,所有用到Timestamp的模型都无需额外修改代码:
import FirebaseFirestore extension Timestamp: Codable { enum CodingKeys: String, CodingKey { case seconds = "_seconds" case nanoseconds = "_nanoseconds" } public init(from decoder: Decoder) throws { // 兼容Firebase Functions返回的带下划线前缀的Timestamp对象 if let container = try? decoder.container(keyedBy: CodingKeys.self) { let seconds = try container.decode(Int64.self, forKey: .seconds) let nanoseconds = try container.decode(Int32.self, forKey: .nanoseconds) self.init(seconds: seconds, nanoseconds: nanoseconds) return } // 兼容ISO 8601格式的日期字符串返回值 let singleContainer = try decoder.singleValueContainer() let dateStr = try singleContainer.decode(String.self) guard let date = ISO8601DateFormatter().date(from: dateStr) else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, debugDescription: "Invalid Timestamp format" )) } self.init(date: date) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(seconds, forKey: .seconds) try container.encode(nanoseconds, forKey: .nanoseconds) } }
方案2:仅为当前CustomModel实现自定义解码逻辑
如果不想影响全局Timestamp的解码规则,可以只在当前模型内单独处理creationTime字段:
struct CustomModel: Codable { var id: String? var creationTime: Timestamp? var providerData: [ProviderData]? var uid: String? enum CodingKeys: String, CodingKey { case id, creationTime, providerData, uid enum CreationTimeCodingKeys: String, CodingKey { case seconds = "_seconds" case nanoseconds = "_nanoseconds" } } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(String.self, forKey: .id) // 单独处理creationTime字段 if let timeContainer = try? container.nestedContainer(keyedBy: CodingKeys.CreationTimeCodingKeys.self, forKey: .creationTime) { let seconds = try timeContainer.decode(Int64.self, forKey: .seconds) let nanoseconds = try timeContainer.decode(Int32.self, forKey: .nanoseconds) creationTime = Timestamp(seconds: seconds, nanoseconds: nanoseconds) } else if let dateStr = try? container.decodeIfPresent(String.self, forKey: .creationTime), let date = ISO8601DateFormatter().date(from: dateStr) { creationTime = Timestamp(date: date) } else { creationTime = nil } providerData = try container.decodeIfPresent([ProviderData].self, forKey: .providerData) uid = try container.decodeIfPresent(String.self, forKey: .uid) } }
方案3:改造Firebase Functions返回格式
如果后端逻辑可控,可在Firebase Functions序列化返回数据时,直接将Timestamp转换为毫秒级时间戳数字,前端解码成本更低:
Node.js Functions侧代码示例:
const data = doc.data(); // 转换Timestamp为毫秒时间戳 const resData = { ...data, creationTime: data.creationTime ? data.creationTime.toMillis() : null }; // 返回resData给前端
Swift端解码时直接将数字转为Timestamp即可,可搭配上述两种方案的自定义逻辑实现。
内容的提问来源于stack exchange,提问作者Bruno
相关产品推荐
相关产品推荐

