Swift中如何解码含可变键与可选值的嵌套JSON字典?
解决可变键metrics数组的Codable兼容问题
问题背景
收到的JSON数据中,metrics数组的每个元素是键名不固定的对象:当某类SPL指标超标时,该元素会额外携带"violation": true字段;未超标时则无此字段。原方案用[[String: Double]]解码会因violation是布尔类型直接失败,需要同时兼容两种结构,且保留指标名称、数值和超标状态。
解决方案1:自定义Metric类型实现Codable
通过自定义Metric结构体和动态编码键,手动处理解码逻辑,完美兼容可变键和可选的violation字段:
// 自定义动态编码键,适配可变的指标名称 struct DynamicCodingKey: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { return nil } } // 单个指标的模型,包含名称、数值、超标状态 struct Metric: Codable { let name: String let value: Double let isViolation: Bool init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: DynamicCodingKey.self) var metricName = "" var metricValue = 0.0 var violation = false // 遍历所有键,分离指标字段和violation字段 for key in container.allKeys { if key.stringValue == "violation" { violation = try container.decode(Bool.self, forKey: key) } else { metricName = key.stringValue metricValue = try container.decode(Double.self, forKey: key) } } self.name = metricName self.value = metricValue self.isViolation = violation } // 可选:如果需要编码回JSON,实现此方法 func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: DynamicCodingKey.self) try container.encode(value, forKey: DynamicCodingKey(stringValue: name)!) if isViolation { try container.encode(true, forKey: DynamicCodingKey(stringValue: "violation")!) } } } // 顶层数据模型 struct SoundMonitorData: Codable { let timestamp: String let deviceName: String let channelName: String let metrics: [Metric] }
解码示例
let jsonData = // 你的原始JSON数据 do { let monitorData = try JSONDecoder().decode(SoundMonitorData.self, from: jsonData) for metric in monitorData.metrics { print("\(metric.name): \(metric.value) | 超标状态: \(metric.isViolation ? "是" : "否")") } } catch { print("解码失败:\(error.localizedDescription)") }
解决方案2:预处理JSON补全默认violation字段
如果不想自定义Codable类型,可以先将JSON转为字典,给每个metrics元素补全"violation": false(已存在则保留原值),再重新编码为Data后解码:
func preprocessMetricsJSON(_ rawData: Data) throws -> Data { guard var rootDict = try JSONSerialization.jsonObject(with: rawData) as? [String: Any] else { throw NSError(domain: "JSONError", code: 0, userInfo: [NSLocalizedDescriptionKey: "无效的JSON结构"]) } guard var metrics = rootDict["metrics"] as? [[String: Any]] else { throw NSError(domain: "JSONError", code: 1, userInfo: [NSLocalizedDescriptionKey: "metrics字段格式错误"]) } // 给每个指标对象补全violation字段,默认false metrics = metrics.map { item in var processedItem = item processedItem["violation"] = item["violation"] ?? false return processedItem } rootDict["metrics"] = metrics return try JSONSerialization.data(withJSONObject: rootDict) }
使用方式
let originalData = // 原始JSON数据 do { let processedData = try preprocessMetricsJSON(originalData) // 此时可以用[[String: Any]]处理,每个字典都包含violation字段 let result = try JSONSerialization.jsonObject(with: processedData) as? [String: Any] let metrics = result?["metrics"] as? [[String: Any]] metrics?.forEach { item in // 提取指标名称(过滤violation) let metricName = item.keys.filter { $0 != "violation" }.first ?? "" let value = item[metricName] as? Double ?? 0.0 let isViolation = item["violation"] as? Bool ?? false print("\(metricName): \(value) | 超标状态: \(isViolation)") } } catch { print("处理失败:\(error.localizedDescription)") }
方案对比
- 方案1:完全符合Swift类型安全规范,代码可维护性高,适合长期项目使用。
- 方案2:无需定义额外模型,快速临时处理场景适用,但类型安全性较弱。
内容的提问来源于stack exchange,提问作者jak the geeker
相关产品推荐
相关产品推荐

