使用Swift的JSONDecoder解析含异构类型字典的JSON问题
如何解析值类型随键变化的JSON数组(Swift JSONDecoder)
这种键对应不同值类型的JSON结构确实容易卡壳,不过我们可以通过自定义Codable实现来完美解决——核心思路是用枚举封装不同的地址条目类型,既适配JSON的多变结构,又能满足Encodable协议要求。
步骤1:定义基础Address结构体
先搞定最基础的地址模型,这个是所有地址类型的通用结构:
struct Address: Codable { let street: String let state: String }
步骤2:用枚举封装不同的地址条目类型
因为JSON里的addresses数组元素是单键值对,但值的类型可能是单个Address或[Address],我们用枚举来对应这些场景:
enum AddressEntry: Codable { // 枚举case对应JSON里的不同键,关联对应的值类型 case home(Address) case work(Address) case favorites([Address]) // 定义JSON里的键名 private enum CodingKeys: String, CodingKey { case home, work, favorites } // 自定义解码逻辑 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 依次检查容器里的键,匹配到哪个就初始化对应的枚举值 if let homeAddr = try container.decodeIfPresent(Address.self, forKey: .home) { self = .home(homeAddr) } else if let workAddr = try container.decodeIfPresent(Address.self, forKey: .work) { self = .work(workAddr) } else if let favoriteAddrs = try container.decodeIfPresent([Address].self, forKey: .favorites) { self = .favorites(favoriteAddrs) } else { throw DecodingError.dataCorruptedError( forKey: container.allKeys.first!, in: container, debugDescription: "无法识别的地址条目类型" ) } } // 自定义编码逻辑,保证符合Encodable协议 func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .home(let addr): try container.encode(addr, forKey: .home) case .work(let addr): try container.encode(addr, forKey: .work) case .favorites(let addrs): try container.encode(addrs, forKey: .favorites) } } }
步骤3:定义主结构体UserProfile
现在可以直接用[AddressEntry]来定义addresses属性,完美适配JSON的数组结构:
struct UserProfile: Codable { let name: String let addresses: [AddressEntry] }
测试解码逻辑
用你的测试JSON来验证一下:
let jsonString = """ { "name":"John Smith", "addresses":[ { "home":{ "street":"123 Main St", "state":"CA" } }, { "work":{ "street":"345 Oak St", "state":"CA" } }, { "favorites":[ { "street":"456 Green St.", "state":"CA" }, { "street":"987 Tambor Rd", "state":"CA" } ] } ] } """ do { let user = try JSONDecoder().decode(UserProfile.self, from: jsonString.data(using: .utf8)!) print("用户名:\(user.name)") for entry in user.addresses { switch entry { case .home(let addr): print("家庭地址:\(addr.street), \(addr.state)") case .work(let addr): print("工作地址:\(addr.street), \(addr.state)") case .favorites(let addrs): print("收藏地址:") addrs.forEach { print("- \($0.street), \($0.state)") } } } } catch { print("解码错误:\(error)") }
这个方案的优势在于:
- 完全遵循
Codable协议,既支持解码也支持编码,解决了你之前遇到的Encodable不兼容问题 - 枚举的case让不同类型的地址条目处理逻辑更清晰,后续扩展新的地址类型(比如
school)只需要加一个枚举case即可
内容的提问来源于stack exchange,提问作者user1118374
相关产品推荐
相关产品推荐

