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
相关产品推荐
相关产品推荐

