在嵌套的Codable
结构中使用解码器时,有什么方法可以访问父结构的属性吗?
我能想到的唯一可行的方法(尚未测试)也是在父结构中使用手动解码器,在userInfo
词典中设置属性,然后在子级中访问userInfo
结构。但这将导致大量样板代码。我希望有一个更简单的解决方案。
struct Item: Decodable, Identifiable {
let id: String
let title: String
let images: Images
struct Images: Decodable {
struct Image: Decodable, Identifiable {
let id: String
let width: Int
let height: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
width = try container.decode(Int.self, forKey: .width)
height = try container.decode(Int.self, forKey: .height)
// How do I get `parent.parent.id` (`Item#id`) here?
id = "\(parent.parent.id)\(width)\(height)"
}
}
let original: Image
let small: Image
// …
}
}
在上面的示例中,仅在JSON的顶级属性中定义了来自服务器的项目ID,但是我也需要在子级中使用它们,因此也可以将它们设置为Identifiable
。
除了使用userInfo
以外,一种方法是将数据附加到init(from decoder: )
。然后可以将其传递到其解码功能中的每个嵌套结构。
struct Item: Decodable, Identifiable {
let id: String
let title: String
let images: Images
struct Images: Decodable {
let original: Image
let small: Image
init(from decoder: Decoder) throws {
throw DecodingError.keyNotFound(Item.CodingKeys.id, DecodingError.Context.init(codingPath: [Item.CodingKeys.id], debugDescription: "Item ID not found in Images"))
}
enum CodingKeys: String, CodingKey {
case original
case small //Note how id has been excluded as a coding key.
}
init(from decoder: Decoder, id: String) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var nestedDecoder = try container.superDecoder(forKey: .original)
self.original = try Image(from: nestedDecoder, id: id)
nestedDecoder = try container.superDecoder(forKey: .small)
self.small = try Image(from: nestedDecoder, id: id)
}
struct Image: Decodable, Identifiable {
let id: String
let width: Int
let height: Int
init(from decoder: Decoder) throws {
throw DecodingError.keyNotFound(Item.CodingKeys.id, DecodingError.Context.init(codingPath: [Item.CodingKeys.id], debugDescription: "Item ID not found in Image"))
}
init(from decoder: Decoder, id: String) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.width = try container.decode(Int.self, forKey: .width)
self.height = try container.decode(Int.self, forKey: .height)
self.id = id
}
enum CodingKeys: String, CodingKey {
case width
case height //Note how id has been excluded as a coding key.
}
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
let nestedDecoder = try container.superDecoder(forKey: .images)
self.images = try Images(from: nestedDecoder, id: self.id)
}
enum CodingKeys: String, CodingKey {
case id
case title
case images
}
}
上述样板的用法如下:
let decoder = JSONDecoder()
let item = decoder.decode(Item.self, from: data)
请注意,userInfo
可能是在现实中实现此目标的最佳方法,因为这会产生很多额外的样板。