5

在嵌套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
        // …
    }
}

在上面的示例中,来自服务器的项目 ID 仅在 JSON 中的顶级属性中定义,但我在子项中也需要它们,因此我也可以将它们设置为Identifiable.

4

1 回答 1

0

我使用 @New Dev 提到的 Itai Ferber 的建议通过以下方式管理它:

  1. 创建一个新的引用类型,其唯一目的是包含一个可以在父子之间传递的可变值。
  2. 将该类型的实例分配给 JSONDecoder 的 userInfo 字典。
  3. 在解码父对象时检索该实例,并将您有兴趣传递的 id 分配给它。
  4. 在对孩子进行解码时,从之前存储在 userInfo 中的实例中检索该 id。

我已将您上面的示例修改如下:

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()
于 2020-08-29T13:08:18.793 回答