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

Swift5中用Codable处理带$前缀的JSON键(如$date)的简便方案

问题背景

在JSON(尤其是Azure/C#相关云服务返回的JSON)中,常能看到这类带特殊命名键的结构:

{
    "Length": 0,
    "TestDate": {
        "$date": "2023-09-20T05:00:00.0000000Z"
    },
    "Weight": 0,
    "Height": 0
}

目前可以通过自定义CodingKeys来解析$date字段,比如:

struct DollarDate: Codable {
    let date: Date
    
    private enum CodingKeys: String, CodingKey {
        case date = "$date"
    }
}

而在Swift中,遇到JSON键是Swift关键字的情况,只需用反引号就能直接映射,无需手动写CodingKeys:

struct Draft: Codable {
    let id: String
    var `case`: Case?   // 直接用反引号即可处理关键字键
}

那么,对于$date这类带特殊符号的JSON键,是否存在类似的简便处理方案?

解答

遗憾的是,目前Swift并没有像处理关键字那样,用反引号就能直接映射$date这类带特殊符号JSON键的简便方案。不过可以通过以下几种优化方式,减少手动编写CodingKeys的繁琐:

1. 复用日期解码逻辑(扩展JSONDecoder)

把识别$date格式的逻辑封装到自定义的JSONDecoder中,全局复用:

// 辅助的动态CodingKey,用于匹配任意字符串键
struct DynamicCodingKey: CodingKey {
    let stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    var intValue: Int?
    init?(intValue: Int) {
        return nil
    }
}

extension JSONDecoder {
    static var azureDateDecoder: JSONDecoder {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .custom { decoder in
            // 解析嵌套的$date结构
            let container = try decoder.container(keyedBy: DynamicCodingKey.self)
            guard let dateKey = DynamicCodingKey(stringValue: "$date"),
                  let dateString = try? container.decode(String.self, forKey: dateKey) else {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "无法解析$date字段"))
            }
            
            let formatter = ISO8601DateFormatter()
            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
            guard let date = formatter.date(from: dateString) else {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "日期格式无效"))
            }
            return date
        }
        return decoder
    }
}

使用时直接调用这个自定义解码器,无需为每个模型写CodingKeys:

struct Measurement: Codable {
    let length: Int
    let testDate: Date
    let weight: Int
    let height: Int
}

// 解码示例
let jsonData = // 你的JSON数据
let measurement = try JSONDecoder.azureDateDecoder.decode(Measurement.self, from: jsonData)

2. 使用属性包装器简化字段解码

封装一个属性包装器,自动处理$date的解码逻辑,模型代码更简洁:

@propertyWrapper
struct DateFromDollarDate: Codable {
    let wrappedValue: Date
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dateString = try container.decode(String.self, forKey: .date)
        
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        guard let date = formatter.date(from: dateString) else {
            throw DecodingError.dataCorruptedError(forKey: .date, in: container, debugDescription: "日期格式无效")
        }
        wrappedValue = date
    }
    
    private enum CodingKeys: String, CodingKey {
        case date = "$date"
    }
}

在模型中直接给日期字段添加该属性包装器即可:

struct Measurement: Codable {
    let length: Int
    @DateFromDollarDate var testDate: Date
    let weight: Int
    let height: Int
}

总结

虽然没有反引号那样一步到位的简便方案,但通过复用解码逻辑或属性包装器,能大幅简化$date这类特殊键的处理,避免重复编写CodingKeys。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 21:46:29