ios - 使用 Codable 使用相同的 key 解码不同的类

标签 ios swift class codable

我正在使用提供 2 个 JSON URL 的 API。每个 URL 都包含一个嵌套容器,其中包含属于同一类和对象的不同属性。

JSON 网址 1

{
  "last_updated": 1535936629,
  "xyz": 5,
  "data": {
    "dataList": [
      {
        "id": "42",
        "a1": "a1value",
        "a2": "a2value",
      },
      // ,,,
    ]
  }
}

JSON 网址 2

{
  "last_updated": 1536639996,
  "xyz": 5,
  "data": {
    "dataList": [
      {
        "id": "42",
        "a3": "a3value",
        "a4": "a4value",
      },
      // ,,,
    ]
  }
}

我想使用这些 JSON URLS 使用嵌套 dataList 列表中的项目创建单个 Codable CustomClass 对象,因此我创建了一个 Feed 结构来处理这两个JSON 文件。

Feed.swift

import Foundation

Struct Feed: Decodable {
  var lastUpdated: Int
  var xyz: Int
  var data: KeyedDecodingContainer<Feed.dataCodingKey>
  var dataList: [CustomClass]

  enum CodingKeys: String, CodingKey {
    case lastUpdated = "last_updated"
    case xyz
    case data
  }

  enum dataCodingKey: String, CodingKey {
    case dataList
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.lastUpdated = try decoder.decode(Int.self, forKey: .lastUpdated)
    self.xyz = try container.decode(Int.self, forKey: .xyz)
    self.data = try container.nestedContainer(keyedBy: dataCodingKey.self, forKey: .data)
    self.dataList = try data.decode([CustomClass].self, forKey: .dataList)
  }
}

CustomClass.swift

class CustomClass: Decodable {

    var id: String
    var a1: String
    var a2: Double
    var a3: String
    var a4: String

    enum CodingKeys: String, CodingKey {
        case id
        case a1
        case a2
        case a3
        case a4
    }

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try values.decode(String.self, forKey: .id)
        self.a1 = try values.decode(String.self, forKey: .a1)
        self.a2 = try values.decode(String.self, forKey: .a2)
        self.a3 = try values.decode(String.self, forKey: .a3)
        self.a4 = try values.decode(String.self, forKey: .a4)
    }
}

在我的 ViewController 中,我执行两个单独的异步调用来获取数据:

ViewController.swift

var listOfCustomClass: [CustomClass]
var listOfCustomClass2: [CustomClass]

func getFeed(urlString: String, completionHandler: @escaping (_ result: Feed?) -> Void) {
    // parses JSON to return a Feed object

    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if error != nil {
            print(error!)
        }
        guard let data = data else { return }

        //Implement JSON decoding and parsing
         do {
            let feed = try JSONDecoder().decode(Feed.self, from: data)
            DispatchQueue.main.async {
                completionHandler(feed)
            }
        } catch {
            print(error)
        }
    }.resume()
}

getFeed(urlString: url1String) { result in
  // Obtain the contents of dataList from URL1
  if let feed = result {
    self.listOfCustomClass = feed.dataList
    self.getFeed(urlString: url2String) { result in
      //Upon completion, obtain the dataList info and populate the "a3" and "a4" attributes from CustomClass

      if let feed = result {
        let dataList2: [CustomClass] = feed.dataList

      // code to merge self.listOfCustomClass 1 and self.listOfCustomClass2 into a single [CustomClass] list with all attributes and store it as self.listOfCustomClass   

        // Upon completion, return the finalized station array for use
        DispatchQueue.main.async {
          completionHandler(self.listOfCustomClass)
        }
      }
    }
  }
}

我遇到的问题是 dataList CodingKey 有不同的键 a1a2 如果来自 URL1 或 a3a4 如果来自 URL2。因此,每当 Codable init 方法无法在 dataList 容器中找到 4 个键中的 2 个时,它就会报错。

如何使用单个解码器实例化 a1、a2、a3 和 a4 来创建一个 CustomClass 对象?

最佳答案

我的建议是使用泛型。在 Feed 中将 dataList 对象的类型作为通用类型传递。您甚至可以使用适当的 dateDecodingStrategy

lastUpdated 解码为 Date
struct Feed<T : Decodable>: Decodable {
    let lastUpdated: Date
    let xyz: Int
    let data: DataList<T>
}

struct DataList<T : Decodable> : Decodable {
    let dataList: [T]
}

dataList 对象的类型可以是任何符合Decodable 的类型,给定的 JSON 可以解码为这两个结构或类:

class CustomClass1 : Decodable {
    let id, a1, a2: String
}

class CustomClass2 : Decodable {
    let id, a3, a4: String
}

多种类型的好处是完全避免了任何键和类型检查。

例如解码第一个JSON写入

let json = """
{
    "last_updated": 1535936629,
    "xyz": 5,
    "data": {
        "dataList": [{"id": "42", "a1": "a1value", "a2": "a2value"}]
    }
}
"""

let data = Data(json.utf8)
do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    decoder.dateDecodingStrategy = .secondsSince1970
    let result = try decoder.decode(Feed<CustomClass1>.self, from: data)
    print(result)
} catch {
    print(error)
}

关于ios - 使用 Codable 使用相同的 key 解码不同的类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52342229/

相关文章:

ios - Swift 保留循环和闭包

c++ - 为类创建带参数的复制构造函数

iphone - 如何在 Objective-C 中设置非实例化类(只有方法的类)

c++ - 基于现有实例创建类的新实例

ios - 发送到释放对象 '_UITextTiledLayer' 对象的消息

ios - 如何更改日期选择器当前日期文本颜色?

ios - 如何仅使用可见 View Controller 的方法响应通知?

ios - 适用于 iOS 的 Facebook SDK 开放图谱故事

ios - 哪个应用程序授权对话框最先显示?推送或定位

ios - 强制当前位置标识符(蓝点)捕捉到最近的路径