6

I have one JSON response from API as follows,

Previous JSON Response:

[
  {
    "EmployeeId": 711,
    "FirstName": "Steve",
    "LastName": "Jobs"
  },
  {
    "EmployeeId": 714,
    "FirstName": "John",
    "LastName": "Doe"
  }
]

and model class for same has following code

class EmployeeModel: Codable {

    let EmployeeId: Int?
    let FirstName: String?
    let LastName: String?
}

for parsing with Swift Codable working fine

do {
    let decodedResponse = try JSONDecoder().decode([EmployeeModel].self, from: response.rawData())
    print(decodedResponse)

} catch let jsonErr {
    print(jsonErr.localizedDescription)
}

but now the

Latest JSON Response

from API is changed and one MiddleName key is added in response see following screenshot and it is also working fine with Swift Codable code. enter image description here

But how can I get notify or print that MiddleName key is now added on JSON response from API in iOS Swift 5?

UPDATE TO QUESTION

According to answer provided below by @CZ54, solution working fine but it is unable to check for another derived class missing key. For example:

enter image description here

// MARK:- LoginModel
class LoginModel: Codable {

    let token: String?
    let currentUser: CurrentUser?
}

// MARK:- CurrentUser
class CurrentUser: Codable {

    let UserName: String?
    let EmployeeId: Int?
    let EmployeeName: String?
    let CompanyName: String?
}
iAj
  • 3,787
  • 1
  • 31
  • 34
  • No error thrown, it's parsed with success.. – iAj Aug 14 '19 at 07:33
  • You try to be warn when you have "new" properties available ? – CZ54 Aug 14 '19 at 07:33
  • I just want to notify or print the missing keys on my Xcode console or any other way – iAj Aug 14 '19 at 07:34
  • @CZ54 can you please let me know with working answer how I can achieve same? – iAj Aug 14 '19 at 07:36
  • This is a discussion you need to have with who ever owns/publish that API primarily, maybe they communicate their changes somehow. – Joakim Danielson Aug 14 '19 at 07:36
  • @JoakimDanielson your point is valid but sometimes in our routine culture they missed to communicate so it's better to have by our own at least for our information we do some implementation at our end.. – iAj Aug 14 '19 at 07:39
  • @JoakimDanielson and for that I am looking for better solution here. – iAj Aug 14 '19 at 07:48
  • for that you should write some api tests and they will notify you about that stuff – Lu_ Aug 14 '19 at 07:53
  • @iAj Why don't you go with `JSONSerialization` to get a list of all keys received in the API? – PGDev Aug 14 '19 at 07:55
  • @PGDev it's better to use latest powerful approach with respect to language up gradation, JSONSerialization is old approach & Swift Codable is latest, so I asked:-) – iAj Aug 14 '19 at 09:13
  • Question further edited, please check now – iAj Aug 14 '19 at 10:37

2 Answers2

8

You can do the following:

let json = """
    {
        "name" : "Jobs",
        "middleName" : "Bob"
    }
"""


class User: Decodable {
    let name: String
}
extension JSONDecoder {
    func decodeAndCheck<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable  {
       let result = try self.decode(type, from: data)

        if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
            let mirror = Mirror(reflecting: result)
            let jsonKeys = json.map { return $0.0 }
            let objectKeys = mirror.children.enumerated().map { $0.element.label }

            jsonKeys.forEach { (jsonKey) in
                if !objectKeys.contains(jsonKey) {
                    print("\(jsonKey) is not used yet")
                }
            }
        }
        return result

    }
}

try JSONDecoder().decodeAndCheck(User.self, from: json.data(using: .utf8)!)

//will print "middleName is not use yet"

CZ54
  • 5,488
  • 1
  • 24
  • 39
  • 4
    Pretty good solution. But you should throw a `DecodingError` if the check fails and also the `JSONSerialization` error – vadian Aug 14 '19 at 08:11
  • That was not the original request, but yes it can be improved – CZ54 Aug 14 '19 at 08:14
  • @CZ54 thanks a lot, working fine with respect to my question but is there any solution for nested Codable class? for Example: let department: Department? where class Department: Codable { let deptId: Int? let deptName: String? } if from response location key will added in Department model class then above solution might not work, please help in this case too – iAj Aug 14 '19 at 09:38
  • @CZ54 can you please check my updated question now? – iAj Aug 14 '19 at 10:20
  • This is not a very useful solution, it only works for the top level object so if you have an array of objects or sub-objects it fails. It will also fail of you modify the property names using `CodingKey`. So all in all a very limited solution – Joakim Danielson Aug 14 '19 at 12:18
-1

You can implement the initializer and check the key if it exists as below,

struct CustomCodingKey: CodingKey {

    let intValue: Int?
    let stringValue: String

    init?(stringValue: String) {
        self.intValue = Int(stringValue)
        self.stringValue = stringValue
    }

    init?(intValue: Int) {
        self.intValue = intValue
        self.stringValue = "\(intValue)"
    }
}

class EmployeeModel: Codable {

    var EmployeeId: Int?
    var FirstName: String?
    var LastName: String?

    private enum CodingKeys: String, CodingKey {
        case EmployeeId, FirstName, LastName
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let allKeysContainer = try decoder.container(keyedBy: CustomCodingKey.self)

        self.EmployeeId = try container.decodeIfPresent(Int.self, forKey: .EmployeeId)
        self.FirstName = try container.decodeIfPresent(String.self, forKey: .FirstName)
        self.LastName = try container.decodeIfPresent(String.self, forKey: .LastName)

        let objectKeys = container.allKeys.map { $0.rawValue}
        for key in allKeysContainer.allKeys {
            if objectKeys.contains(key.stringValue) == false {
                print("Key: " + key.stringValue + " is added!")
            }
        }
    }
}
Kamran
  • 14,987
  • 4
  • 33
  • 51