Search code examples
iosswiftdictionarystruct

How to create model to decode API response if key have value type dictionary of String format


This is Post API response which I am getting from postman here checklist done have two keys "checklist_done", "work_with_price":

{
    "additional_data": {
            "id": 215,
            "booking_id": 221,
            "checklist_done": "{\"4\":\"Gear Oil\",\"2\":\"Engine Oil Change\",\"3\":\"Air and Oil Filter\"}",
            "work_with_price": "{\"chain\":\"145\",\"Battery\":\"58\"}",
            "work_price": "203",
            "estimated_price": 149,
            "discount_code": "admin",
            "mechanic_profit": "200",
            "discount": 0,
            "ccavenue_charge": 0,
            "ccavenue_gst": 0,
            "total_price": "352",
            "payment_method": null,
            "gateway": null,
            "payment_response": null,
            "status": "pending",
            "paymentmode": null,
            "bankname": null,
            "txnid": null,
            "banktxnid": null,
            "txndate": null,
            "created_at": "2023-04-27T18:20:07.000000Z",
            "updated_at": "2023-04-27T18:27:19.000000Z",
            "admin_discount": 0
        }
    }

I have created model like below but JsonDecoder is unable to map response to my created model. I marked checklistDone and workWithPrice as String?.

struct AdditionalData: Codable {
    var id, bookingID: Int?
    var checklistDone: String?
    var workWithPrice: String?
    var workPrice: String?
    var estimatedPrice: Int?
    var discountCode, mechanicProfit: String?
    var discount, ccavenueCharge, ccavenueGst: Int?
    var totalPrice: String?
    var paymentMethod, gateway, paymentResponse: String?
    var status: String?
    var paymentmode, bankname: String?
    var txnid, banktxnid: String?
    var txndate: String?
    var createdAt, updatedAt: String?
    var adminDiscount: Int?

    enum CodingKeys: String, CodingKey {
        case id
        case bookingID = "booking_id"
        case checklistDone = "checklist_done"
        case workWithPrice = "work_with_price"
        case workPrice = "work_price"
        case estimatedPrice = "estimated_price"
        case discountCode = "discount_code"
        case mechanicProfit = "mechanic_profit"
        case discount
        case ccavenueCharge = "ccavenue_charge"
        case ccavenueGst = "ccavenue_gst"
        case totalPrice = "total_price"
        case paymentMethod = "payment_method"
        case gateway
        case paymentResponse = "payment_response"
        case status
        case paymentmode, bankname, txnid, banktxnid, txndate
        
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case adminDiscount = "admin_discount"
    }
}

Please guide me the model I have created is wrong or anything else.


Solution

  • I have used below model and I am able to decode the model.

    struct APIData: Codable {
        let additionalData: AdditionalData?
    
        enum CodingKeys: String, CodingKey {
            case additionalData = "additional_data"
        }
    }
    
    struct AdditionalData : Codable {
    
        let adminDiscount : Int?
        let bankname : String?
        let banktxnid : String?
        let bookingId : Int?
        let ccavenueCharge : Int?
        let ccavenueGst : Int?
        let checklistDone : String?
        let createdAt : String?
        let discount : Int?
        let discountCode : String?
        let estimatedPrice : Int?
        let gateway : String?
        let id : Int?
        let mechanicProfit : String?
        let paymentMethod : String?
        let paymentResponse : String?
        let paymentmode : String?
        let status : String?
        let totalPrice : String?
        let txndate : String?
        let txnid : String?
        let updatedAt : String?
        let workPrice : String?
        let workWithPrice : String?
    
    
        enum CodingKeys: String, CodingKey {
            case adminDiscount = "admin_discount"
            case bankname = "bankname"
            case banktxnid = "banktxnid"
            case bookingId = "booking_id"
            case ccavenueCharge = "ccavenue_charge"
            case ccavenueGst = "ccavenue_gst"
            case checklistDone = "checklist_done"
            case createdAt = "created_at"
            case discount = "discount"
            case discountCode = "discount_code"
            case estimatedPrice = "estimated_price"
            case gateway = "gateway"
            case id = "id"
            case mechanicProfit = "mechanic_profit"
            case paymentMethod = "payment_method"
            case paymentResponse = "payment_response"
            case paymentmode = "paymentmode"
            case status = "status"
            case totalPrice = "total_price"
            case txndate = "txndate"
            case txnid = "txnid"
            case updatedAt = "updated_at"
            case workPrice = "work_price"
            case workWithPrice = "work_with_price"
        }
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            adminDiscount = try values.decodeIfPresent(Int.self, forKey: .adminDiscount)
            bankname = try values.decodeIfPresent(String.self, forKey: .bankname)
            banktxnid = try values.decodeIfPresent(String.self, forKey: .banktxnid)
            bookingId = try values.decodeIfPresent(Int.self, forKey: .bookingId)
            ccavenueCharge = try values.decodeIfPresent(Int.self, forKey: .ccavenueCharge)
            ccavenueGst = try values.decodeIfPresent(Int.self, forKey: .ccavenueGst)
            checklistDone = try values.decodeIfPresent(String.self, forKey: .checklistDone)
            createdAt = try values.decodeIfPresent(String.self, forKey: .createdAt)
            discount = try values.decodeIfPresent(Int.self, forKey: .discount)
            discountCode = try values.decodeIfPresent(String.self, forKey: .discountCode)
            estimatedPrice = try values.decodeIfPresent(Int.self, forKey: .estimatedPrice)
            gateway = try values.decodeIfPresent(String.self, forKey: .gateway)
            id = try values.decodeIfPresent(Int.self, forKey: .id)
            mechanicProfit = try values.decodeIfPresent(String.self, forKey: .mechanicProfit)
            paymentMethod = try values.decodeIfPresent(String.self, forKey: .paymentMethod)
            paymentResponse = try values.decodeIfPresent(String.self, forKey: .paymentResponse)
            paymentmode = try values.decodeIfPresent(String.self, forKey: .paymentmode)
            status = try values.decodeIfPresent(String.self, forKey: .status)
            totalPrice = try values.decodeIfPresent(String.self, forKey: .totalPrice)
            txndate = try values.decodeIfPresent(String.self, forKey: .txndate)
            txnid = try values.decodeIfPresent(String.self, forKey: .txnid)
            updatedAt = try values.decodeIfPresent(String.self, forKey: .updatedAt)
            workPrice = try values.decodeIfPresent(String.self, forKey: .workPrice)
            workWithPrice = try values.decodeIfPresent(String.self, forKey: .workWithPrice)
        }
    
    
    }
    

    Your root object should be APIData while you are decoding because you have an root object in your response above AdditionalData, see below code.

    let jsonDecoder = JSONDecoder()
            do{
                let response = try jsonDecoder.decode(APIData.self, from: data)
                completion(response, nil)
            }catch(let error){
                completion(nil,error)
            }