Decoding JSON - nil result, no errors

467 Views Asked by At

It seems like the decoding process stops at the point of getting the values from the container in the init method and not even showing any errors. What am I missing?


    func getUserInfo(userId: Int, completion: @escaping (User?, Error?) -> Void) {
            
            guard let token = Session.shared.token else { return }
            
            let configuration = URLSessionConfiguration.default
            
            let session =  URLSession(configuration: configuration)
    
            var urlConstructor = URLComponents()
            urlConstructor.scheme = "https"
            urlConstructor.host = "api.vk.com"
            urlConstructor.path = "/method/users.get"
            urlConstructor.queryItems = [
                URLQueryItem(name: "user_ids", value: "\(userId)"),
                URLQueryItem(name: "fields", value: "bdate"),
                URLQueryItem(name: "access_token", value: "\(token)"),
                URLQueryItem(name: "v", value: "5.68")
            ]
            
            let decoder = JSONDecoder()
            
            let task = session.dataTask(with: urlConstructor.url!) { (data, response, error) in
                
                let jsonData = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments)
                debugPrint("jsonData:", jsonData)
                
                guard let dataResponse = data, error == nil else {
                    debugPrint(error?.localizedDescription ?? "Response Error")
                    return }
                
                do {
                    
                    let result = try decoder.decode(User.self, from: dataResponse)
                    debugPrint("result:", result)
                    completion(result, nil)
                    
                } catch (let error) {
                    
                    completion(nil, error)
                }
            }
            
            task.resume()
    
        }

The json data I get looks like this:

"jsonData:" Optional({
    response =     (
                {
            bdate = "22.9.2000";
            "first_name" = Toyota;
            id = 616595796;
            "last_name" = Camry;
        }
    );
})

Below is the code for the struct. Decoding stops at the line starting with "self.id" and the end result of the "getUserInfo" func is nil.


    struct User: Decodable {
        let id: Int
        let firstName: String
        let lastName: String
        let birthDate: Double
        
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            self.id = try values.decode(Int.self, forKey: .id)
            self.firstName = try values.decode(String.self, forKey: .firstName)
            self.lastName = try values.decode(String.self, forKey: .lastName)
            self.birthDate = try values.decode(Double.self, forKey: .birthDate)            
        }
        
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case birthDate = "bdate"
    }

1

There are 1 best solutions below

1
On

Nothing you have in your struct is optional, but your JSON is missing two keys. If your struct requires all four properties to be present, you won't get a valid instance of said struct unless all four properties are indeed present.

What you need to do is change your struct to make the properties that are optional into optionals and use decodeIfPresent in your init method. You can even nil coalesce at that point too if you want default values.

But you're failing because your data doesn't match your struct.

Try something like this:

struct User: Decodable {
    let id: Int
    let firstName: String?
    let lastName: String?
    let birthDate: Double
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try values.decode(Int.self, forKey: .id)
        self.firstName = try values.decodeIfPresent(String.self, forKey: .firstName)
        self.lastName = try values.decodeIfPresent(String.self, forKey: .lastName)
        self.birthDate = try values.decode(Double.self, forKey: .birthDate)
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case birthDate = "bdate"
    }
}