Свойство доступа родительской структуры во вложенной структуре Codable при декодировании дочерней
При использовании декодера во вложенном Codable
struct, есть ли способ получить доступ к свойству родительской структуры?
Единственный способ, которым я могу представить, что это может сработать (еще не тестировал), - это использовать ручной декодер в родительской структуре, установив свойство в 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, но они мне нужны и в дочерних элементах, поэтому я также могу сделать их Identifiable
.
1 ответ
Мне удалось это с помощью предложения Итаи Фербера, упомянутого @New Dev, следующим образом:
- Создайте новый ссылочный тип, единственная цель которого - содержать изменяемое значение, которое может передаваться между родительским и дочерним.
- Назначьте экземпляр этого типа в словарь userInfo JSONDecoder.
- Получите этот экземпляр при декодировании родителя и назначьте ему идентификатор, который вы хотите передать.
- Во время декодирования дочернего элемента извлеките этот идентификатор из экземпляра, хранящегося в userInfo ранее.
Я изменил ваш пример выше следующим образом:
struct Item: Decodable, Identifiable {
enum CodingKeys: String, CodingKey {
case id
case title
case images
}
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)
if let referenceTypeUsedOnlyToContainAChangeableIdentifier = decoder.userInfo[.referenceTypeUsedOnlyToContainAChangeableIdentifier] as? ReferenceTypeUsedOnlyToContainAChangeableIdentifier {
self.id = referenceTypeUsedOnlyToContainAChangeableIdentifier.changeableIdentifier
} else {
self.id = "something went wrong"
}
}
}
let original: Image
let small: Image
// …
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
if let referenceTypeUsedOnlyToContainAChangeableIdentifier = decoder.userInfo[.referenceTypeUsedOnlyToContainAChangeableIdentifier] as? ReferenceTypeUsedOnlyToContainAChangeableIdentifier {
referenceTypeUsedOnlyToContainAChangeableIdentifier.changeableIdentifier = id
}
}
}
}
// Use this reference type to just store an id that's retrieved later.
class ReferenceTypeUsedOnlyToContainAChangeableIdentifier {
var changeableIdentifier: String?
}
// Convenience extension.
extension CodingUserInfoKey {
static let referenceTypeUsedOnlyToContainAChangeableIdentifier = CodingUserInfoKey(rawValue: "\(ReferenceTypeUsedOnlyToContainAChangeableIdentifier.self)")!
}
let decoder = JSONDecoder()
// Assign the reference type here to be used later during the decoding process first to assign the id in `Item` and then
// later to retrieve that value in `Images`
decoder.userInfo[.referenceTypeUsedOnlyToContainAChangeableIdentifier] = ReferenceTypeUsedOnlyToContainAChangeableIdentifier()