如何借助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
相关产品推荐
相关产品推荐

