解码可空异构字典:KeyedDecodingContainer扩展实现求助
解决异构对象为键的字典解码问题
首先,我注意到你现有的字典解码代码存在核心问题:你用了nestedUnkeyedContainer处理字典,但逻辑上没有适配键值对结构——而且原生JSON不支持非字符串作为字典键,这类场景下后端通常会把异构键的字典序列化为包含key和value字段的对象数组(比如[{"key": {...}, "value": 1.0}, ...])。
下面是适配这个场景的完整解决方案,同时复用你已实现的异构对象解码逻辑:
1. 定义辅助结构体与CodingKeys
先创建辅助结构体来解码数组中的单个键值对条目,同时定义对应的CodingKeys:
// 辅助结构体,用于解码JSON中的单个字典条目 private struct DictionaryEntry<T: Decodable>: Decodable { let key: T let value: CGFloat } private extension KeyedDecodingContainer { // 用于解析键值对条目的CodingKeys enum EntryCodingKeys: String, CodingKey { case key, value } }
2. 修正后的字典解码方法
我们先解码数组形式的条目,再将其转换为Swift字典,同时复用你已有的异构对象解码逻辑:
func decodeDictIfPresent<T : Decodable, U : ClassFamily>(family: U.Type, forKey key: K) throws -> [T: CGFloat]? { do { // 获取存储字典的数组容器 var arrayContainer = try self.nestedUnkeyedContainer(forKey: key) var entries = [DictionaryEntry<T>]() while !arrayContainer.isAtEnd { // 解码单个键值对的容器 let entryContainer = try arrayContainer.nestedContainer(keyedBy: EntryCodingKeys.self) // 复用你已实现的单个异构对象解码逻辑来解析key guard let decodedKey = try entryContainer.decodeIfPresent(family: family, forKey: .key) else { continue // 跳过无法解析的条目 } // 解码CGFloat类型的value let decodedValue = try entryContainer.decode(CGFloat.self, forKey: .value) entries.append(DictionaryEntry(key: decodedKey, value: decodedValue)) } // 将数组转换为字典:用reduce保留最后一个重复key的值 return entries.reduce(into: [T: CGFloat]()) { result, entry in result[entry.key] = entry.value } // 如果要严格保证key唯一,用下面的代码(重复key会抛出错误) // return Dictionary(uniqueKeysWithValues: entries.map { ($0.key, $0.value) }) } catch DecodingError.keyNotFound, DecodingError.valueNotFound { return nil } }
关键注意事项
- JSON结构匹配:确保后端返回的JSON结构是
[{"key": {...}, "value": 数值}, ...]的形式,如果你的JSON结构不同(比如直接用字符串键对应异构对象的ID),需要调整解码逻辑先通过ID映射到异构对象。 - 异构类型兼容性:确保
T是所有异构对象类型的共同父类或协议,并且U.getType()返回的类型可以安全转换为T.Type,否则会跳过无法解析的条目。 - 重复key处理:上面的代码用
reduce保留最后一个重复key的值,如果你的场景不允许重复key,可以改用Dictionary(uniqueKeysWithValues:),但重复key会抛出解码错误。
内容的提问来源于stack exchange,提问作者Will Alexander
相关产品推荐
相关产品推荐

