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

如何用Codable解码含动态对象的嵌套JSON键

处理动态键和多类型值的Codable字典方案

我之前也碰到过一模一样的问题——动态键、值类型还不固定的字典要实现Codable,确实头疼,因为[String:Any]没法直接用,Any不遵循Codable协议。给你两个亲测有效的方案,你可以根据场景选:

方案一:用枚举封装所有可能的值类型(类型安全首选)

先定义一个枚举,把所有可能的值类型都包含进去,让它自己实现Codable协议,这样就能和字典结合使用了:

enum DynamicValue: Codable {
    case string(String)
    case stringArray([String])
    case intArray([Int])
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // 依次尝试解码不同类型,匹配到就赋值
        if let singleString = try? container.decode(String.self) {
            self = .string(singleString)
        } else if let stringList = try? container.decode([String].self) {
            self = .stringArray(stringList)
        } else if let intList = try? container.decode([Int].self) {
            self = .intArray(intList)
        } else {
            throw DecodingError.dataCorruptedError(
                in: container,
                debugDescription: "Unsupported value type for DynamicValue"
            )
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        // 编码时根据枚举类型对应处理
        switch self {
        case .string(let value):
            try container.encode(value)
        case .stringArray(let value):
            try container.encode(value)
        case .intArray(let value):
            try container.encode(value)
        }
    }
}

然后你的模型就可以这样写:

struct YourResponseModel: Codable {
    let testMap: [String: DynamicValue]
    // 其他字段...
}

使用的时候,通过switch就能安全地取出对应类型的值,完全不用怕类型转换错误:

switch response.testMap["someKey"] {
case .string(let str):
    print("Got string: \(str)")
case .stringArray(let arr):
    print("Got string array: \(arr)")
case .intArray(let arr):
    print("Got int array: \(arr)")
case nil:
    print("Key not found")
}

这个方案的好处是类型绝对安全,编译期就能帮你检查错误,后续维护也清晰。如果以后新增了值类型,只要在枚举里加个case就行。

方案二:手动实现Codable,直接用[String:Any](灵活但需注意类型安全)

如果一定要用[String:Any],可以手动实现init(from:)和encode(to:)方法,配合动态CodingKey来遍历处理每个键值对:

首先定义一个动态CodingKey的结构体:

struct DynamicCodingKey: CodingKey {
    var stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    
    var intValue: Int?
    init?(intValue: Int) {
        return nil // 我们只处理字符串键,这里返回nil就行
    }
}

然后在你的模型里手动实现Codable:

struct YourResponseModel: Codable {
    let testMap: [String: Any]
    
    enum CodingKeys: String, CodingKey {
        case testMap
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 获取testMap对应的嵌套容器
        let testMapContainer = try container.nestedContainer(keyedBy: DynamicCodingKey.self, forKey: .testMap)
        
        var tempMap = [String: Any]()
        // 遍历所有动态键
        for key in testMapContainer.allKeys {
            // 依次尝试解码不同类型
            if let strValue = try? testMapContainer.decode(String.self, forKey: key) {
                tempMap[key.stringValue] = strValue
            } else if let strArray = try? testMapContainer.decode([String].self, forKey: key) {
                tempMap[key.stringValue] = strArray
            } else if let intArray = try? testMapContainer.decode([Int].self, forKey: key) {
                tempMap[key.stringValue] = intArray
            }
            // 要是有其他类型,在这里继续加判断就行
        }
        testMap = tempMap
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        var testMapContainer = container.nestedContainer(keyedBy: DynamicCodingKey.self, forKey: .testMap)
        
        for (key, value) in testMap {
            guard let codingKey = DynamicCodingKey(stringValue: key) else { continue }
            // 编码时根据值类型处理
            if let strValue = value as? String {
                try testMapContainer.encode(strValue, forKey: codingKey)
            } else if let strArray = value as? [String] {
                try testMapContainer.encode(strArray, forKey: codingKey)
            } else if let intArray = value as? [Int] {
                try testMapContainer.encode(intArray, forKey: codingKey)
            } else {
                throw EncodingError.invalidValue(
                    value,
                    EncodingError.Context(
                        codingPath: [codingKey],
                        debugDescription: "Unsupported value type for encoding"
                    )
                )
            }
        }
    }
}

这个方案的好处是不需要额外的枚举类型,直接用[String:Any],但使用的时候你得自己做类型判断,容易出现类型转换失败的情况,适合对灵活性要求高、且能确保值类型范围的场景。

注意事项

  • 如果后续有新的值类型(比如布尔值、数字),记得在对应的解码/编码逻辑里添加处理;
  • 解码时如果遇到未知类型,最好抛出明确的错误,方便调试,不要默默忽略;
  • 优先推荐方案一,类型安全带来的维护性提升真的很有用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:03:18