解码子项时嵌套Codable结构中父结构的访问属性

问题描述 投票:0回答:1

在嵌套的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

swift struct decoding codable identifiable
1个回答
0
投票

除了使用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可能是在现实中实现此目标的最佳方法,因为这会产生很多额外的样板。

© www.soinside.com 2019 - 2024. All rights reserved.