Edgar
Edgar

Reputation: 179

How can I use decodable in unspecified return data from API

when trying to parse return data from API im getting "The data couldn’t be read because it isn’t in the correct format." because the return is inconsistent.

When logo_url has value it was a object see example below:

"logo_url": {
                    "mime_type": "image/jpeg",
                    "url": "http://google.com"
                },

But when it doenst have value its return empty array

"logo_url": [],

This is the reason why im getting "The data couldn’t be read because it isn’t in the correct format."

My model

struct Model: Decodable {
    let logo: Logo?

    enum CodingKeys: String, CodingKey {
        case logo = "logo_url"
    }
}

struct Logo: Decodable {
    
    let mimeType: String?
    let url: String?
    
    enum CodingKeys: String, CodingKey {
        case mimeType = "mime_type"
        case url
    }
}

Upvotes: 0

Views: 108

Answers (2)

congnd
congnd

Reputation: 1274

I personally prefer checking if the logo_url is an array first, then let Swift report error if there is any happens by using try instead of try? when trying to decode a key. Since in most cases, you may want to know why your decoding failed instead of just getting nil as a result.

Additionally, you may want to use .convertFromSnakeCase as a keyDecodingStrategy so you don't have to write extra code.

let json2 = """
{
  "logo_url": {
    "mime_type": "image/jpeg",
    "url": "http://google.com"
  }
}
"""

let json3 = "[]"

struct Logo: Decodable {
  let mimeType: String
  let url: String
}

struct Model: Decodable {
  let logo: Logo?

  private enum CodingKeys: String, CodingKey {
      case logoUrl
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    if (try? container.decode([String].self, forKey: .logoUrl)) != nil {
      self.logo = nil
    } else {
      self.logo = try container.decode(Logo.self, forKey: .logoUrl)
    }
  }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let result2 = try decoder.decode(Model.self, from: json2.data(using: .utf8)!)
print(result2)

let result3 = try? decoder.decode(Model.self, from: json3.data(using: .utf8)!)
print(result3)

Upvotes: 1

New Dev
New Dev

Reputation: 49590

If you can't change this badly written API, you'd need a custom decoder, where you basically attempt to decode as the type you want, and failing that - make it nil:

struct Model: Decodable {
    let logo: Logo?

    enum CodingKeys: String, CodingKey {
        case logo = "logo_url"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        if let logo = try? container.decode(Logo.self, forKey: .logo) {
            self.logo = logo
        } else {
            self.logo = nil
        }
    }
}

Upvotes: 1

Related Questions