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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 19:06:03