快速解码单个对象与对象数组的JSON

时间:2020-03-19 22:26:19

标签: arrays json swift decodable

我对快速编程语言还很陌生,并且一直在努力使它在上个星期左右可用。我正在使用一个返回JSON数据的现有API,该数据的结构根据返回的场所数而略有变化。

实际结构有些复杂,但是此示例说明了该问题。一种结果是,我得到了一个返回的场地,例如:

{
  "totalItems": 21,
  "pageSize": 2,
  "venues": {
    "venue":
    {
       "name": "Venue Name 1"
       "location": "Location A",
       "showCount": "4"
    }
  }
}

另一种结果是,我得到了一系列返回场地:

{
  "totalItems": 21,
  "pageSize": 2,
  "venues": {
    "venue":
    [{
       "name": "Venue Name 1"
       "location": "Location A",
       "showCount": "4"
    },
    {
       "name": "Venue Name 2"
       "location": "Location B",
       "showCount": "2"
    }]
  }
}

是的-该API的所有者应该返回一个数组,无论如何,但它们没有,并且不能更改。

我能够针对一系列场所对它进行正确解码(或者即使没有通过任何场所),但是当通过单个场所时,它会中止(由于结构的变化)。当我更改代码以容纳单个场所时,我的代码也起作用了,但是随后放弃了多个场所的返回。

我想做的就是将任何一种变体解码为一个包含数组的内部结构,而不管我收到哪种变体,从而使事后处理起来对于我来说要简单得多。像这样:

struct Response: Decodable {
    let totalItems: Int
    let pageSize: Int
    let venues: VenueWrapper

    struct VenueWrapper: Decodable {
        let venue: [Venue]     // This might contain 0, 1, or more than one venues
    }

    struct Venue: Decodable {
        let name: String
        let location: String
        let showCount: Int
    }
}

注意:在实际的JSON响应中,响应中实际上有几个这样的子结构(例如,单个结构与结构的数组),这就是为什么我觉得仅创建替代结构并不是一个好的解决方案。 / p>

我希望以前有人遇到过这个问题。预先感谢!

2 个答案:

答案 0 :(得分:2)

您可以创建自己的解码器,

struct Response: Decodable {
    let totalItems: Int
    let pageSize: Int
    let venues: VenueWrapper

    struct VenueWrapper: Decodable {
        var venue: [Venue]

        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            venue = []
            if let singleVenue = try? values.decode(Venue.self, forKey: CodingKeys.venue) {
                //if a single venue decoded append it to array
                venue.append(singleVenue)
            } else if let multiVenue = try? values.decode([Venue].self, forKey: CodingKeys.venue) {
                //if a multi venue decoded, set it as venue
                venue = multiVenue
            }

            enum CodingKeys: String, CodingKey { case venue }
        }
    }

    struct Venue: Decodable {
        let name: String
        let location: String
        let showCount: String
    }
}

答案 1 :(得分:1)

不需要VenueWrapper。 ??

struct Response {
  let totalItems: Int
  let pageSize: Int
  let venues: [Venue]

  struct Venue {
    let name: String
    let location: String
    let showCount: Int
  }
}

您需要编写自己的初始化程序。不幸的是,我不认为有一种方法可以消除未经编码的属性的样板。

即使您永远不需要对其进行编码,制作Response Codable而不只是Decodable也会使您可以访问自动生成的CodingKeys

extension Response: Codable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    totalItems = try container.decode(Int.self, forKey: .totalItems)
    pageSize = try container.decode(Int.self, forKey: .pageSize)
    venues = try container.decode(key: .venues)
  }
}

最后一行依赖于协议和扩展名,您可以将其用于类似“编码”的任何其他类型。 ?

protocol GoofilyEncoded: Codable {
  /// Must have exactly one case.
  associatedtype GoofyCodingKey: CodingKey
}

extension KeyedDecodingContainer {
  func decode<Decodable: GoofilyEncoded>(key: Key) throws -> [Decodable] {
    let nestedContainer = try self.nestedContainer(
      keyedBy: Decodable.GoofyCodingKey.self,
      forKey: key
    )

    let key = nestedContainer.allKeys.first!

    do {
      return try nestedContainer.decode([Decodable].self, forKey: key)
    }
    catch {
      return try [nestedContainer.decode(Decodable.self, forKey: key)]
    }
  }
}

所有可能编码为数组的类型(或不是 ?‍?️)都将需要一个单例枚举,如下所示:

extension Response.Venue: GoofilyEncoded {
  enum GoofyCodingKey: CodingKey {
    case venue
  }
}
相关问题