【问题标题】:Decoding JSON for single object vs array of objects in swift快速解码单个对象与对象数组的 JSON
【发布时间】:2020-07-01 02:31:42
【问题描述】:

我对 swift 编程语言相当陌生,并且在过去一周左右的时间里一直在努力让它工作。我正在使用一个现有的 API,它返回 JSON 数据,其结构会根据返回的场地数量而略有变化。

实际结构稍微复杂一些,但这个例子说明了问题。在一种结果中,我得到一个返回的场地,例如:

{
  "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 响应中,响应中实际上有几个这样的子结构(例如,单个结构与结构数组),这就是为什么我觉得简单地创建一个替代结构不是一个好的解决方案。

我希望以前有人遇到过这种情况。提前致谢!

【问题讨论】:

  • 你能提供一个更接近现实的例子来说明你必须使用什么吗?您缺少逗号,因此这不是有效的 JSON。并且您的 Response 无法按原样解码,因为 showCounts 是字符串。
  • 感谢您的快速回复。实际的 JSON 要复杂得多,所以我尝试对其进行修剪,以专注于真正的问题,即数组或非数组部分。在我自己的代码中,我添加了将字符串转换为整数的自定义逻辑,但没有包含它。
  • 抱歉 - 在我准备好之前,“返回”触发了我的评论。 (我仍然是在这里发帖的新手。)我试图避免发布整个 json,因为它很长。我可能应该删除 showCount 项目周围的引号,因为它会影响问题。

标签: arrays json swift decodable


【解决方案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
  }
}

【讨论】:

  • 你说得对——我真的不想要 VenueWrapper,但它反映了我正在传递的 JSON 结构。我喜欢你的方法,因为它摆脱了不需要的结构,更重要的是,它帮助我更好地创建了一种通用方法来覆盖我在同一个 API 响应中使用其他元素时遇到的类似情况。我将不得不对此进行更多研究以了解如何使用这种方法,因为我还精通这种风格(还)。我的代码的其他部分也可以使用相同类型的通用设计技能,因此值得付出努力。感谢您的帮助!
【解决方案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
    }
}

【讨论】:

  • 行得通!!!我在正确的道路上,但你已经优雅地解决了这个问题。非常感谢!!
猜你喜欢
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多