ios - 使用 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)
}
}
}
}
}
我遇到的问题是dataList
CodingKey 有不同的键a1
,或者a2
如果来自 URL1 a3
,或者a4
如果来自 URL2。因此,当 Codable init 方法在容器中找不到 4 个键中的 2 个时,它就会抱怨dataList
。
如何使用单个解码器实例化 a1、a2、a3 和 a4 创建一个 CustomClass 对象?
解决方案
我的建议是使用泛型。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)
}