Search code examples
jsonswiftnestedalamofiredecodable

How to decode nested json on to a custom class with array in Swift?


I am getting nil when trying to parse a nested json response onto a custom decodable response class.

Custom Response Classes:

class User: Decodable, Encodable {

    var name: String?
    var email: String?
    var token: String?

    enum CodingKeys: String, CodingKey {
        case name
        case email
        case token
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try? container.decode(String.self, forKey: .name)
        self.email = try? container.decode(String.self, forKey: .email)
        self.token = try? container.decode(String.self, forKey: .token)
    }
}

class ResponseData: Decodable {

    let body: [User]?

    enum CodingKeys: String, CodingKey {
        case users
        case body
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let response = try container.nestedContainer(keyedBy:CodingKeys.self, forKey: .body)
        self.body = try response.decode([User].self, forKey: .users)
    }
}

class ResponseRoot: Decodable {
    let data : ResponseData?

    enum CodingKeys: String, CodingKey { case data }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.data = try? container.decode(ResponseData.self, forKey: .data)
    }
}

Json Response for Parsing,

{
    "status": "success",
    "errorMessage": null,
    "data": {
        "headers": {},
        "body": [
            {
                "name": "Alex",
                "email": "[email protected]",
                "password": "1234",
                "token": "1234",
                "loginStatus": 0
            },
            {
                "name": "Einstein",
                "email": "[email protected]",
                "password": "1234",
                "token": "A valid token",
                "loginStatus": 1
            }
        ],
        "statusCode": "OK",
        "statusCodeValue": 200
    }
}

Alamofire Call,

Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.queryString, headers: nil)
         .validate()
         .responseJSON { response in

            switch (response.result) {

                case .success( _):

                do {
                    let users = try JSONDecoder().decode(ResponseRoot.self, from: response.data!) // getting users = nil
                    completion((users.data?.body!)!)

                } catch let error as NSError {
                    print("Failed to load: \(error.localizedDescription)")
                    completion([])
                }

                 case .failure(let error):
                    print("Request error: \(error.localizedDescription)")
                    completion([])
             }

Now, let users = try JSONDecoder().decode(ResponseRoot.self, from: response.data!) does not create any exception but users is nil.


Solution

  • The structs you made are (in my opinion) overly complicated. They could be the following:

    class User: Codable {
    
        var name: String
        var email: String
        var token: String
    }
    
    class ResponseData: Codable {
    
        let body: [User]?
    }
    
    class ResponseRoot: Codable {
        let data : ResponseData
    }
    

    Then just call JSONDecoder().decode(ResponseRoot.self, from: data) inside of a try catch block.