skip to Main Content

I am trying to parse a json string in which some of the keys are not fixed. There are some keys which are related to error and either it will be error or the result data. Followings are the two examples:

{
    "ok": true,
    "result": {
        "code": "694kyH",
        "short_link": "shrtco.de/694kyH",
        "full_short_link": "https://shrtco.de/694kyH",
        "short_link2": "9qr.de/694kyH",
        "full_short_link2": "https://9qr.de/694kyH",
        "short_link3": "shiny.link/694kyH",
        "full_short_link3": "https://shiny.link/694kyH",
        "share_link": "shrtco.de/share/694kyH",
        "full_share_link": "https://shrtco.de/share/694kyH",
        "original_link": "http://google.com"
    }
}

{
    "ok": false,
    "error_code": 2,
    "error": "This is not a valid URL, for more infos see shrtco.de/docs"
}

How will I parse this JSON. I have tried to build my class like following but it is not working:

struct ShortLinkData: Codable {
    let ok: Bool
    let result: Result?
    let errorCode: Int?
    let error: String?
    
    private enum CodingKeys : String, CodingKey { case ok, result, errorCode = "error_code", error }

       init(from decoder: Decoder) throws {
          let container = try decoder.container(keyedBy: CodingKeys.self)
          ok = try container.decode(Bool.self, forKey: .ok)
           result = try container.decode(Result.self, forKey: .result)
           errorCode = try container.decodeIfPresent(Int.self, forKey: .errorCode)
           error = try container.decodeIfPresent(String.self, forKey: .error)
      }
}

// MARK: - Result
struct Result: Codable {
    let code, shortLink: String
    let fullShortLink: String
    let shortLink2: String
    let fullShortLink2: String
    let shortLink3: String
    let fullShortLink3: String
    let shareLink: String
    let fullShareLink: String
    let originalLink: String

    enum CodingKeys: String, CodingKey {
        case code
        case shortLink = "short_link"
        case fullShortLink = "full_short_link"
        case shortLink2 = "short_link2"
        case fullShortLink2 = "full_short_link2"
        case shortLink3 = "short_link3"
        case fullShortLink3 = "full_short_link3"
        case shareLink = "share_link"
        case fullShareLink = "full_share_link"
        case originalLink = "original_link"
    }
    init(from decoder: Decoder) throws {
       let container = try decoder.container(keyedBy: CodingKeys.self)
        code = try container.decode(String.self, forKey: .code)
        shortLink = try container.decode(String.self, forKey: .shortLink)
        fullShortLink = try container.decode(String.self, forKey: .fullShortLink)
        shortLink2 = try container.decode(String.self, forKey: .shortLink2)
        fullShortLink2 = try container.decode(String.self, forKey: .fullShortLink2)
        shortLink3 = try container.decode(String.self, forKey: .shortLink3)
        fullShortLink3 = try container.decode(String.self, forKey: .fullShortLink3)
        shareLink = try container.decode(String.self, forKey: .shareLink)
        fullShareLink = try container.decode(String.self, forKey: .fullShareLink)
        originalLink = try container.decode(String.self, forKey: .originalLink)
   }
}

My parsing code:

let str = String(decoding: data, as: UTF8.self)
print(str)
let shortURL = try? JSONDecoder().decode(ShortLinkData.self, from: data)
return shortURL!

I am always getting nil in shortURL object.

2

Answers


  1. You should split this into several steps in order to avoid to handle all these optionals in your model.

    First create a struct that has only those properties that are guaranteed to be there. ok in your case:

    struct OKResult: Codable{
        let ok: Bool
    }
    

    then create one for your error state and one for your success state:

    struct ErrorResult: Codable{
        let ok: Bool
        let errorCode: Int
        let error: String
        
        private enum CodingKeys: String, CodingKey{
            case ok, errorCode = "error_code", error
        }
    }
    
    struct ShortLinkData: Codable {
        let ok: Bool
        let result: Result
    }
    
    struct Result: Codable {
        let code, shortLink: String
        let fullShortLink: String
        let shortLink2: String
        let fullShortLink2: String
        let shortLink3: String
        let fullShortLink3: String
        let shareLink: String
        let fullShareLink: String
        let originalLink: String
    
        enum CodingKeys: String, CodingKey {
            case code
            case shortLink = "short_link"
            case fullShortLink = "full_short_link"
            case shortLink2 = "short_link2"
            case fullShortLink2 = "full_short_link2"
            case shortLink3 = "short_link3"
            case fullShortLink3 = "full_short_link3"
            case shareLink = "share_link"
            case fullShareLink = "full_share_link"
            case originalLink = "original_link"
        }
    }
    

    Then you can decode the data:

    guard try JSONDecoder().decode(OKResult.self, from: data).ok else{
        let errorResponse = try JSONDecoder().decode(ErrorResult.self, from: data)
        //handle error scenario
        fatalError(errorResponse.error) // or throw custom error or return nil etc...
    }
    
    let shortlinkData = try JSONDecoder().decode(ShortLinkData.self, from: data)
    

    Remarks:

    • Your inits are not necessary.
    • Never use try? this will hide all errors from you
    • you would need to wrap this either in a do catch block or make your function throwing and handle errors further up the tree.
    Login or Signup to reply.
  2. Actually there are no optional fields. The server sends two different but distinct JSON strings.

    A suitable way to decode both JSON strings is an enum with associated values. It decodes the ok key, then it decodes either the result dictionary or errorCode and error

    enum Response : Decodable {
        case success(ShortLinkData), failure(Int, String)
        
        private enum CodingKeys : String, CodingKey { case ok, result, errorCode, error }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let ok = try container.decode(Bool.self, forKey: .ok)
            if ok {
                let result = try container.decode(ShortLinkData.self, forKey: .result)
                self = .success(result)
            } else {
                let errorCode = try container.decode(Int.self, forKey: .errorCode)
                let error = try container.decode(String.self, forKey: .error)
                self = .failure(errorCode, error)
            }
        }
    }
    

    In ShortLinkData the init method and the CodingKeys are redundant if you specify the convertFromSnakeCase key decoding strategy

    struct ShortLinkData: Decodable {
        let code, shortLink: String
        let fullShortLink: String
        let shortLink2, fullShortLink2: String
        let shortLink3, fullShortLink3: String
        let shareLink, fullShareLink: String
        let originalLink: String
    }
    
    
    do {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let result = try decoder.decode(Response.self, from: data)
        switch result {
            case .success(let linkData): print(linkData)
            case .failure(let code, let message): print("An error occurred with code (code) and message (message)")
        }
    } catch {
        print(error)
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search