问题
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