2

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?

1
  • You could have 2 optional properties: let name: String?; let otherName: String?, and when you want to access the property, you could use a computed property: var nameToUse: String { name ?? otherName }? Commented Jul 26, 2022 at 16:06

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.