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

如何借助Codable从Dictionary直接初始化遵循协议的结构体实例?

解决方案:借助Decodable直接从[String:Any]字典初始化结构体

可以实现一个自定义字典解码器,直接基于[String:Any]数据解码Decodable结构体,无需经过JSON/PList的往返转换,也不用为每个结构体编写自定义初始化器。

自定义DictionaryDecoder实现

class DictionaryDecoder {
    private let userInfo: [CodingUserInfoKey: Any]
    
    init(userInfo: [CodingUserInfoKey: Any] = [:]) {
        self.userInfo = userInfo
    }
    
    func decode<T: Decodable>(_ type: T.Type, from dictionary: [String: Any]) throws -> T {
        let container = DictionaryDecodingContainer(dictionary: dictionary, userInfo: userInfo)
        return try T(from: container)
    }
}

private class DictionaryDecodingContainer: Decoder {
    let dictionary: [String: Any]
    let userInfo: [CodingUserInfoKey: Any]
    
    init(dictionary: [String: Any], userInfo: [CodingUserInfoKey: Any]) {
        self.dictionary = dictionary
        self.userInfo = userInfo
    }
    
    var codingPath: [CodingKey] = []
    
    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
        let keyedContainer = DictionaryKeyedDecodingContainer<Key>(dictionary: dictionary, codingPath: codingPath, userInfo: userInfo)
        return KeyedDecodingContainer(keyedContainer)
    }
    
    func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Unkeyed containers not supported"))
    }
    
    func singleValueContainer() throws -> SingleValueDecodingContainer {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Single value containers not supported"))
    }
}

private class DictionaryKeyedDecodingContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
    typealias Key = Key
    
    let dictionary: [String: Any]
    var codingPath: [CodingKey]
    let userInfo: [CodingUserInfoKey: Any]
    
    init(dictionary: [String: Any], codingPath: [CodingKey], userInfo: [CodingUserInfoKey: Any]) {
        self.dictionary = dictionary
        self.codingPath = codingPath
        self.userInfo = userInfo
    }
    
    var allKeys: [Key] {
        dictionary.keys.compactMap { Key(stringValue: $0) }
    }
    
    func contains(_ key: Key) -> Bool {
        dictionary.keys.contains(key.stringValue)
    }
    
    func decodeNil(forKey key: Key) throws -> Bool {
        dictionary[key.stringValue] is NSNull
    }
    
    func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
        guard let value = dictionary[key.stringValue] else {
            throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Key not found"))
        }
        
        if let value = value as? T {
            return value
        } else if let convertibleValue = value as? Decodable, let typedValue = convertibleValue as? T {
            return typedValue
        } else {
            throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Could not convert value to \(T.self)"))
        }
    }
    
    func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
        guard let nestedDict = dictionary[key.stringValue] as? [String: Any] else {
            throw DecodingError.typeMismatch([String: Any].self, DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Value is not a dictionary"))
        }
        let container = DictionaryKeyedDecodingContainer<NestedKey>(dictionary: nestedDict, codingPath: codingPath + [key], userInfo: userInfo)
        return KeyedDecodingContainer(container)
    }
    
    func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Unkeyed containers not supported"))
    }
    
    func superDecoder() throws -> Decoder {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Super decoder not supported"))
    }
    
    func superDecoder(forKey key: Key) throws -> Decoder {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Super decoder not supported"))
    }
}

使用示例

struct Point: Codable {
    let x: Int
    let y: Int
}

let pointDictionary = [ "x": 0, "y": 1]

struct Score: Codable {
    let points: Int
    let name: String
}

let scoreDictionary: [String: Any] = [
    "points": 10,
    "name": "iOSDevZone"
]

// 初始化解码器
let decoder = DictionaryDecoder()

// 解码Point
do {
    let point = try decoder.decode(Point.self, from: pointDictionary)
    print("Point: \(point.x), \(point.y)") // 输出:Point: 0, 1
} catch {
    print("解码Point失败:\(error)")
}

// 解码Score
do {
    let score = try decoder.decode(Score.self, from: scoreDictionary)
    print("Score: \(score.points), \(score.name)") // 输出:Score: 10, iOSDevZone
} catch {
    print("解码Score失败:\(error)")
}

方案优势

  • 一次实现解码器,所有遵循Codable协议的结构体都能直接使用,无需为每个结构体编写自定义初始化器,适配大量结构体的场景。
  • 直接处理[String:Any]字典,无需转换为JSON Data或PList,避免额外的序列化/反序列化开销。
  • 可扩展支持嵌套结构体、数组等复杂类型(只需补充对应解码逻辑),能处理来自不合格JSON转换后的字典数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:28:09