使用自定义初始化程序解码 Swift 5 中的嵌套对象

Decode a nested object in Swift 5 with custom initializer

我有一个 API,其中 returns 一个这样的有效载荷(示例中只包含一个项目)。

{
  "length": 1,
  "maxPageLimit": 2500,
  "totalRecords": 1,
  "data": [
    {
      "date": "2021-05-28",
      "peopleCount": 412
    }
  ]
}

我知道我实际上可以创建一个像

这样的结构
struct Root: Decodable {
  let data: [DailyCount]
}

struct DailyCount: Decodable {
  let date: String
  let peopleCount: Int
}

对于不同的调用,相同的API returns根的格式相同,只是数据不同而已。此外,我不需要根信息(lengthtotalRecordsmaxPageLimit)。 因此,我正在考虑在 struct DailyCount 中创建自定义初始化,以便我可以在我的 URL 会话

中使用它
let reports = try! JSONDecoder().decode([DailyCount].self, from: data!)

使用 Swift 5 我试过这个:

struct DailyCount: Decodable {
  let date: String
  let peopleCount: Int
}

extension DailyCount {
  enum CodingKeys: String, CodingKey {
    case data
    enum DailyCountCodingKeys: String, CodingKey {
      case date
      case peopleCount
    }
  }
  init(from decoder: Decoder) throws {
    // This should let me access the `data` container
    let container = try decoder.container(keyedBy: CodingKeys.self
    peopleCount = try container.decode(Int.self, forKey: . peopleCount)
    date = try container.decode(String.self, forKey: .date)
  }
}

不幸的是,它不起作用。我遇到两个问题:

  1. 该结构似乎不再符合 Decodable 协议
  2. CodingKeys不包含peopleCount(因此returns出错)

由于多种原因,这行不通。您正在尝试解码一个数组,因此根本不会调用 DailyCount 中的自定义解码实现(如果要编译的话),因为在顶层,您的 JSON 包含一个对象,而不是一个数组。

但是有一个更简单的解决方案,甚至不需要您自己实现 Decodable。

您可以为您的外部对象创建一个通用的包装器结构,并将其用于您需要的任何负载类型:

 struct Wrapper<Payload: Decodable>: Decodable {
     var data: Payload
 }

然后您可以使用它来解码您的 DailyCount 结构数组:

let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data

通过在 JSON解码器上创建扩展可以使这更加透明:

extension JSONDecoder {
     func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
          try decode(Wrapper<T>.self, from: data).data
     }
}

Sven 的回答纯粹而优雅,但如果我没有指出还有一种愚蠢但简单的方法,那就是我的失职:根本不使用 Codable 的垃圾箱-潜入 "data"。示例:

// preconditions
let json = """
{
  "length": 1,
  "maxPageLimit": 2500,
  "totalRecords": 1,
  "data": [
    {
      "date": "2021-05-28",
      "peopleCount": 412
    }
  ]
}
"""
let jsonData = json.data(using: .utf8)!
struct DailyCount: Decodable {
    let date: String
    let peopleCount: Int
}

// okay, here we go
do {
    let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable:Any]
    let arr = dict?["data"] as? Array<Any>
    let json2 = try JSONSerialization.data(withJSONObject: arr as Any, options: [])
    let output = try JSONDecoder().decode([DailyCount].self, from: json2)
    print(output) // yep, it's an Array of DailyCount
} catch {
    print(error)
}