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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 14:29:56