如何用单个Swift CodingKeys解析含重复键的JSON数据?
问题:Swift Decodable解析含重复"name"键的JSON
Unsplash API返回的JSON片段如下:
{ "exif": { "name": "Canon, EOS 6D" }, "location": { "name": "Antelope Canyon, United States" }, "user": { "name": "Joe Gardner" } }
尝试解析为Swift Decodable结构体时,编写了如下代码,但遇到CodingKeys不允许重复键名的问题:
struct WallpaperResponse: Decodable { var location: String? var camera: String? var username: String enum CodingKeys: String, CodingKey { case locationName = "name" case cameraName = "name" case username = "name" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // Decode location let locationContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .location) location = try locationContainer.decodeIfPresent(String.self, forKey: .locationName) // ... // Decode camera details let exifContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .exif) camera = try exifContainer.decode(String.self, forKey: .cameraName) // Decode user data let userContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user) username = try userContainer.decode(String.self, forKey: .username) } }
请问是否可以在不创建CodingKeys2这类额外枚举的情况下,使用单个CodingKeys解析这类含重复键的JSON?
解决方案
完全可以不用额外的CodingKeys枚举,以下两种实现方式都能解决问题:
方式一:利用动态CodingKey直接解析
只在CodingKeys里定义外层的键,解析嵌套容器时使用通用的动态编码键获取name字段,无需重复枚举:
struct WallpaperResponse: Decodable { var location: String? var camera: String? var username: String enum CodingKeys: String, CodingKey { case exif, location, user } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解析location的name if let locationContainer = try container.nestedContainerIfPresent(keyedBy: DynamicCodingKey.self, forKey: .location) { location = try locationContainer.decode(String.self, forKey: DynamicCodingKey(stringValue: "name")!) } // 解析exif的name let exifContainer = try container.nestedContainer(keyedBy: DynamicCodingKey.self, forKey: .exif) camera = try exifContainer.decode(String.self, forKey: DynamicCodingKey(stringValue: "name")!) // 解析user的name let userContainer = try container.nestedContainer(keyedBy: DynamicCodingKey.self, forKey: .user) username = try userContainer.decode(String.self, forKey: DynamicCodingKey(stringValue: "name")!) } } // 通用动态编码键,无需额外枚举 struct DynamicCodingKey: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue } let intValue: Int? init?(intValue: Int) { self.intValue = intValue self.stringValue = String(intValue) } }
方式二:嵌套小型结构体(更简洁)
通过定义私有嵌套结构体匹配JSON子结构,利用Decodable自动解码能力,再通过计算属性映射到外层属性:
struct WallpaperResponse: Decodable { // 计算属性映射嵌套结构体的name值 var location: String? { locationInfo?.name } var camera: String? { exif?.name } var username: String { user.name } // 私有嵌套结构体,自动匹配JSON子结构 private struct Exif: Decodable { let name: String } private struct Location: Decodable { let name: String } private struct User: Decodable { let name: String } private let exif: Exif? private let locationInfo: Location? private let user: User enum CodingKeys: String, CodingKey { case exif case locationInfo = "location" case user } }
这种方式完全无需自定义解码逻辑,代码可读性和维护性更高。
内容的提问来源于stack exchange,提问作者Daniel Crompton
相关产品推荐
相关产品推荐

