I am calling an API and then decoding it with the simplified code below
guard let url = URL(string: "someURL") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
let decoder = JSONDecoder()
if let data = data {
do {
let results = try decoder.decode(Response.self, from: data)
print(results)
} catch {
print(error)
}
}
}
task.resume()
Where Response is my struct seen below
struct Response : Codable {
let response: ResponseContents
}
struct ResponseContents : Codable {
let result : [wantedData]
}
struct wantedData : Codable {
let name: String
}
For the most part this works well however, sometimes the API returns a JSON that does not have a key called name and instead the key is otherName - therefore I get an error saying 'keyNotFound'.
Is there a way I can add a conditional statement in my struct or parsing statement that checks to see if the key is not found and if not it uses another one that I define?
CodePudding user response:
The solution given from Larme is good as well.
But you also can use Decodable with CodingKey directly, harder to read, but reusable.
struct DynamicKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
}
struct wantedData : Codable {
let name: String
init(from decoder: Decoder) throws {
let dynamicKeysContainer = try decoder.container(keyedBy: DynamicKey.self)
try dynamicKeysContainer.allKeys.forEach { key in
switch key.stringValue {
case "name" where try dynamicKeysContainer.decode(String.self, forKey: key):
name = $0
case "otherName" where try dynamicKeysContainer.decode(String.self, forKey: key):
name = $0
default: break
}
}
}
}
I didn't try it. And you can probably do better, just post that to help.
CodePudding user response:
You just need an extra decoding key, and a manual decoder:
For Data like this, where "name" and "otherName" are possible parameters:
let json = Data("""
[
{
"name": "Alice",
"age": 43
},
{
"otherName": "Bob",
"age": 25
}
]
""".utf8)
And a struct like this:
struct Person {
var name: String
var age: Int
}
Then decoding looks like this:
extension Person: Decodable {
enum CodingKeys: CodingKey {
case name
case otherName // Keys do not have to be the same as properties
case age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Try both approaches:
self.name = try container.decodeIfPresent(String.self, forKey: .name) ??
container.decode(String.self, forKey: .otherName)
// Other, default handling
self.age = try container.decode(Int.self, forKey: .age)
}
}