When using a decoder in a nested Codable
struct, is there any way to access a property of a parent struct?
The only way I can think of that might work (haven't tested yet) is to use a manual decoder in the parent struct too, set the property in the userInfo
dictionary, and then access userInfo
in the child struct. But that would result in a lot of boilerplate code. I'm hoping there's a simpler solution.
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
// …
}
}
In the above example, the item ID coming from the server is only defined in the top-level properties in the JSON, but I need them in the children too, so I can also make them Identifiable
.
I managed it using Itai Ferber's suggestion as mentioned by @New Dev in the following way:
I've modified your example above as follows:
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With