Search code examples
iosswiftencodecodabledecodable

Parsing Json with Decoding Protocol (Codable)


I am trying to parse JSON using Codable protocol but getting exception. This exception is coming due to null value in response which of nestedkeyedcontainer for second response.

Its working fine for below json response

    let jsonStr = """
   {
     "response": {
         "PURCHASE_FREE_RESPONSE": {
                                "TOPUP": null,
                                "TOTAL_PAYMENT_AMOUNT": "0",
                                "TAX": null,
                                "VAT": null,
                               ........
                                 "CUSTOM_FIELD_2": null,
                                 "CUSTOM_FIELD_3": null,
                                 "FEE": null
                  }
         },
     "API-Code": "ttttt",
     "statuscode": 200,
     "statusMessage": "SUCCESS",
     "respCode": {
     "ERROR_CODE": "0",
     "ERROR_DESC": "Success"
      }
     }
    """
    //=========Second Response===============

        let jsonStr = """
         {
            **"response": null,**
            "API-Code": null,
            "statuscode": 500,
            "statusMessage": "Undefined index: HOUSE_NO",
              **"respCode": null**
           }
 
        """

My Model looks like:-

struct SimPurResponse:Decodable{
let topUp:String
let totalPaymentAmt:String
let transId:String


let apiCode:String
let statuscode:Int
let statusMessage:String

let errorCode:String
let errorDesc:String


private enum ResponseOuter : String, CodingKey {
    case response
    case apiCode      = "API-Code"
    case statuscode   = "statuscode"
    case respCode
    case statusMessage = "statusMessage"
}

enum RespCodeKeys : String, CodingKey {
    case errorCode   = "ERROR_CODE"
    case errorDesc   = "ERROR_DESC"
}

private enum Response : String, CodingKey {
    case PURCHASE_FREE_SIM_RESPONSE
}

private enum purchaseFreeSimResInfo : String, CodingKey {
    case topUp            = "TOPUP"
    case totalPaymentAmt  = "TOTAL_PAYMENT_AMOUNT"
    case transId          = "TRANSACTION_ID"
    case tax              = "TAX"
    case vat              =  "VAT"
    case shipingAmt       = "SHIPPING_AMOUNT"
    
}


init(from decoder :Decoder) throws {
    let container     = try decoder.container(keyedBy: ResponseOuter.self)
    self.apiCode      = try container.decodeIfPresent(String.self, forKey: .apiCode) ?? ""
    self.statuscode    = try container.decodeIfPresent(Int.self, forKey: .statuscode) ?? 600
    self.statusMessage = try container.decodeIfPresent(String.self, forKey: .statusMessage) ?? ""
    
    let respCodeCont =  try container.nestedContainer(keyedBy: RespCodeKeys.self, forKey: .respCode)
    self.errorCode   =  try respCodeCont.decodeIfPresent(String.self, forKey: .errorCode) ?? ""
    self.errorDesc   =  try respCodeCont.decodeIfPresent(String.self, forKey: .errorDesc) ?? ""
    
   
    let purcCont          = try container.nestedContainer(keyedBy: Response.self, forKey: .response)
    let purSimCont        = try purcCont.nestedContainer(keyedBy: purchaseFreeSimResInfo.self, forKey: .PURCHASE_FREE_SIM_RESPONSE)

      self.topUp            = try purSimCont?.decodeIfPresent(String.self,  forKey: .topUp) ?? ""
     self.totalPaymentAmt    = try purSimCont?.decodeIfPresent(String.self, forKey: .totalPaymentAmt) ?? ""
     self.transId           = try purSimCont?.decodeIfPresent(String.self, forKey: .transId) ?? ""
     self.tax              = try purSimCont?.decodeIfPresent(String.self, forKey: .tax) ?? ""
     self.vat               = try purSimCont?.decodeIfPresent(String.self, forKey: .vat) ?? ""
     self.shipingAmt       = try purSimCont?.decodeIfPresent(String.self, forKey: .shipingAmt) ?? ""
    
}

}

        let dataJosn = Data(jsonStr.utf8)

 do {
     let simDetail = try JSONDecoder().decode(SimPurResponse.self, from: dataJosn)

   print(simDetail)


    }catch let error {
      print(error)
    }

I am getting problem in handling both situation. Above complete code


Solution

  • Just change your 'try' to 'try?' when accessing nestedContainer in the decoder initializer block, and add optional chaining where needed:

    init(from decoder :Decoder) throws {
    
        let data = try decoder.container(keyedBy: ResponseOuter.self)
    
        self.apiCode      = try data.decodeIfPresent(String.self, forKey: .apiCode) ?? ""
        self.statuscode    = try data.decodeIfPresent(Int.self, forKey: .statuscode) ?? 600
        self.statusMessage = try data.decodeIfPresent(String.self, forKey: .statusMessage) ?? ""
    
        let respCodeCont =  try? data.nestedContainer(keyedBy: RespCodeKeys.self, forKey: .respCode)
        self.errorCode   =  try respCodeCont?.decodeIfPresent(String.self, forKey: .errorCode) ?? ""
        self.errorDesc   =  try respCodeCont?.decodeIfPresent(String.self, forKey: .errorDesc) ?? ""
    
        let purcCont          = try? data.nestedContainer(keyedBy: Response.self, forKey: .response)
        let purSimCont        = try purcCont?.nestedContainer(keyedBy: purchaseFreeSimResInfo.self, forKey: .PURCHASE_FREE_SIM_RESPONSE)
        self.topUp            = try purSimCont?.decodeIfPresent(String.self, forKey: .topUp) ?? ""
        self.totalPaymentAmt    = try purSimCont?.decodeIfPresent(String.self, forKey: .totalPaymentAmt) ?? ""
        self.transId           = try purSimCont?.decodeIfPresent(String.self, forKey: .transId) ?? ""
        self.tax              = try purSimCont?.decodeIfPresent(String.self, forKey: .tax) ?? ""
        self.vat               = try purSimCont?.decodeIfPresent(String.self, forKey: .vat) ?? ""
        self.shipingAmt       = try purSimCont?.decodeIfPresent(String.self, forKey: .shipingAmt) ?? ""
    
        self.itgTransId          = try purSimCont?.decodeIfPresent(String.self, forKey: .itgTransId) ?? ""
        self.chanelTransId       = try purSimCont?.decodeIfPresent(String.self, forKey: .chanelTransId) ?? ""
        self.paymentResponse     = try purSimCont?.decodeIfPresent(String.self, forKey: .paymentResponse) ?? ""
        self.apmResponse        = try purSimCont?.decodeIfPresent(String.self, forKey: .apmResponse) ?? ""
        self.applicationType    = try purSimCont?.decodeIfPresent(String.self, forKey: .applicationType) ?? ""
        self.nameorPageId       = try purSimCont?.decodeIfPresent(String.self, forKey: .nameorPageId) ?? ""
        self.url                = try purSimCont?.decodeIfPresent(String.self, forKey: .url) ?? ""
    
        self.customField1       = try purSimCont?.decodeIfPresent(String.self, forKey: .customField1) ?? ""
        self.customField2       = try purSimCont?.decodeIfPresent(String.self, forKey: .customField2) ?? ""
        self.customField3       = try purSimCont?.decodeIfPresent(String.self, forKey: .customField3) ?? ""
        self.fee                 = try purSimCont?.decodeIfPresent(String.self, forKey: .fee) ?? ""
    }
    

    Hope this helps. Cheers.