问题
I'm working with an API that provides 2 JSON URLS. Each URL contains a nested container with different attributes that belong to the same class and object.
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",
},
// ,,,
]
}
}
I want to use these JSON URLS to create a single Codable CustomClass object using the items in the nested dataList list, so I created a Feed struct to handle these 2 JSON files.
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)
}
}
In my ViewController, I do two separate asynchronous calls to obtain the data:
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)
}
}
}
}
}
The problem I'm running into is that the dataList CodingKey has different keys a1 or a2 if coming from URL1 or a3, a4 if coming from URL2. Therefore the Codable init method is complaining whenever it can't find 2 of 4 keys in the dataList container.
How can I approach creating one CustomClass object with a1, a2, a3, and a4 instantiated using a single Decoder?
回答1:
If the JSON to CustomClass may or may or may not contain keys a1, a2, etc, then they must be optional…
let a1: String?
let a2: Double?
let a3: String?
let a4: String?
Then it's just a case of using
a1 = try values.decodeIfPresent(String.self, forKey: .a1)
回答2:
My suggestion is to use generics. Pass the type of the dataList object as generic type in Feed. You can even decode lastUpdated to Date with the appropriate dateDecodingStrategy
struct Feed<T : Decodable>: Decodable {
let lastUpdated: Date
let xyz: Int
let data: DataList<T>
}
struct DataList<T : Decodable> : Decodable {
let dataList: [T]
}
The type of the dataList object can be anything which conforms to Decodable, the given JSON can be decoded to these two structs or classes:
class CustomClass1 : Decodable {
let id, a1, a2: String
}
class CustomClass2 : Decodable {
let id, a3, a4: String
}
The benefit of multiple types is the complete avoiding of any key and type checking.
For example to decode the first JSON write
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)
}
来源:https://stackoverflow.com/questions/52342229/using-codable-to-decode-different-classes-using-the-same-key