Eilon
Eilon

Reputation: 2982

Swift - Decodable fails to decode with an optional value

I'm trying to make a simple sign-in request to a backend. The backend returns a JSON object consisting of a status code, a string message every time, and an access token only if the sign-in was successful.
For that, I have created the following struct:

struct AuthResponse: Decodable {
    enum CodingKeys: String, CodingKey {
        case code, message
        case accessToken = "access_token"
    }

    let code: Int, message: String, accessToken: String?
}

When the sign-in is unsuccessful and an access token is not sent by the server, the AuthResponse object manages to decode properly, with nil for the accessToken property, just as I expect it to. For example, the decode is successful for the following response:

code = 400;
message = "User not registered";

But for a response with an access token, like this one:

"access_token" = TheAccessToken;
code = 200;
message = "Login successfully";

The AuthResponse object does not fails to decode.

Why does this happen? Does that have to do with the fact that the accessToken property is optional? I have also tried overriding the decoding initializer and implementing a decodeIfPresent(_:forKey:) and unwrapping the property but both didn't seem to help.
Thanks in advance.

Upvotes: 0

Views: 1087

Answers (1)

vadian
vadian

Reputation: 285069

The code must not fail with your given information.

Please print the error instance in the catch block and add it to your question.

Nevertheless a sophisticated alternative – without any optional – is an enum with associated types. It returns the token on success and the code and message on failure

enum AuthResponse: Decodable {

    case success(String), failure(Int, String)

    private enum CodingKeys : String, CodingKey { case code, message, accessToken = "access_token" }

    init(from decoder : Decoder) throws
    {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            let code = try container.decode(Int.self, forKey: .code)
            if code == 200 {
                let token = try container.decode(String.self, forKey: .accessToken)
                self = .success(token)
            } else {
                let message = try container.decode(String.self, forKey: .message)
                self = .failure(code, message)
            }
        }
    }
}

and use it

let result = try JSONDecoder().decode(AuthResponse.self, from: data)
switch result {
   case .success(let token): print(token)
   case .failure(let code, let message): print(code, message)
}

Upvotes: 2

Related Questions