首页 > 解决方案 > 使用 Codable 使用相同的密钥解码不同的类

问题描述

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

JSON URL 1

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

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

我遇到的问题是dataListCodingKey 有不同的键a1,或者a2如果来自 URL1 a3,或者a4如果来自 URL2。因此,当 Codable init 方法在容器中找不到 4 个键中的 2 个时,它就会抱怨dataList

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

标签: iosswiftclasscodable

解决方案


我的建议是使用泛型。dataList将对象的类型作为泛型类型传递给Feed. 您甚至lastUpdated可以Date使用适当的解码dateDecodingStrategy

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

推荐阅读