Nitish
Nitish

Reputation: 14113

Creating model for JSON where key is a value

JSON :

{  
    "rows" :
    [
        {
            "_id": "5cdc0ede5c3dcb04bdb3a972",
            "emp_code": 187,
            "log_id": 361711,
            "punch_time": "2019-05-07T04:00:33.000Z",
            "pin_type": 1,
            "status": 4,
            "__v": 0
        },
        {
            "_id": "5cdc40de5c3dcb04bdb3a972",
            "emp_code": 111,
            "log_id": 361701,
            "punch_time": "2019-05-07T04:00:35.000Z",
            "pin_type": 101,
            "status": 4,
            "__v": 0
        }
    ],
    "pin_type_text": {
        "1": "In Fingerprint",
        "4": "In Card",
        "101": "Out Fingerprint",
        "104": "Out Card"
    }
}  

The value of pin_type in each row refers to the record in pin_type_text mapped with it's key.

I am using AlamofireObjectMapper for creating models, and here is the PinTypeText model :

class PinTypeText : Mappable {

    var inFingerprint: String?
    var inCard: String?
    var outFingerprint: String?
    var outCard: String?

    required init?(map: Map) {

    }

    func mapping(map: Map) {
        self.inFingerprint <- map["1"]
        self.inCard <- map["4"]
        self.outFingerprint <- map["101"]
        self.outCard <- map["104"]
    }  
}  

Issue : Suppose in future, the pin_type values - 1, 4, 101, 104 change in the backend, how can I handle such a case without changing my model. As per this model structure, I need to change my model class every time the backend model changes

Upvotes: 1

Views: 100

Answers (1)

PGDev
PGDev

Reputation: 24341

Here is how you can use Codable as a solution,

1. Create a model Row that will contain the data of a single row in rows array of json, i.e.

class Row: Decodable {
    var id: String?
    var pinType: String?
    var pinId: Int?

    enum CodingKeys: String, CodingKey {
        case id = "_id"
        case pinId = "pin_type"
    }
}

In the above model, I've used 2 different properties - pinType and pinId.

  1. pinId will contain the pin_type value in the row

  2. pinType will contain the actual value corresponding to pinId. We'll fill this value later.

Also, I've only used a small set of keys of the row. You can add more as required.

2. Next create another model Response that will contain an array of Row, i.e.

class Response: Decodable {
    var rows: [Row]?

    enum CodingKeys: String, CodingKey {
        case rows, pin_type_text
    }

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        rows = try values.decodeIfPresent([Row].self, forKey: .rows)
        let pinTypeText = try values.decodeIfPresent([String:String].self, forKey: .pin_type_text)
        rows?.forEach({ (row) in
            if let pinId = row.pinId {
                row.pinType = pinTypeText?[String(pinId)]
            }
        })
    }
}

In the above model,

  1. rows array in json is parsed as [Row].

  2. pinTypeText dictionary is parsed as [String:String] type.

  3. [Row] is enumerated to fill pinType in each row using pinId and pinTypeText dictionary.

When using, you need to use pinType property of a Row object.

response?.rows?.forEach({ print($0.pinType) }) //This line will print - "In Fingerprint" and "Out Fingerprint"

Let me know in case you face issue implementing this approach.

Upvotes: 2

Related Questions