Home > database >  How do I handle errors within a struct when parsing JSON in Swift
How do I handle errors within a struct when parsing JSON in Swift

Time:07-27

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