【问题标题】:Decode a nested object in Swift 5 with custom initializer使用自定义初始化程序在 Swift 5 中解码嵌套对象
【发布时间】:2022-01-01 18:43:59
【问题描述】:

我有一个 API 可以返回这样的有效负载(示例中只包含一项)。

{
  "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 返回相同的根格式,但数据不同。此外,我不需要根信息(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(因此返回错误)

【问题讨论】:

    标签: json swift struct


    【解决方案1】:

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

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

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

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

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

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

    这可以通过在 JSONDecoder 上创建一个扩展来变得更加透明:

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

    【讨论】:

      【解决方案2】:

      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)
      }
      

      【讨论】:

      • 当然,这行得通。但是解析 JSON 数据以将其中的一部分转回数据只是为了再次解析有点浪费。
      • 我完全同意!尽管如此,它一直都在完成,这是一个值得一提的简单解决方案。
      • 我试过这个,尽管我在帖子中没有提到。我发现它浪费资源而不是真正的解决方案。如果我需要考虑它,那么嵌套结构的初始简单实现会更好。
      猜你喜欢
      • 1970-01-01
      • 2013-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-30
      • 2015-02-08
      • 2020-07-28
      • 1970-01-01
      相关资源
      最近更新 更多