Swift Codable decode empty json as nil or empty object

Makalele picture Makalele · Feb 23, 2018 · Viewed 13.8k times · Source

Here's my code:

class LoginUserResponse : Codable {
    var result: String = ""
    var data: LoginUserResponseData?
    var mess: [String] = []
}

public class LoginUserResponseData : Codable {
    var userId = "0"
    var name = ""
}

Now, calling the server API I'm parsing response like this (using Stuff library to simplify parsing):

do {
    let loginUserResponse = try LoginUserResponse(json: string)
} catch let error {
    print(error)
}

When I enter the correct password I'm getting an answer like this:

{"result":"success","data":{"userId":"10","name":"Foo"},"mess":["You're logged in"]}

This is fine, the parser is working correctly.

While providing wrong password gives the following answer:

{"result":"error","data":{},"mess":["Wrong password"]}

In this situation, the parser is failing. It should set data to nil, but instead, it tries to decode it to the LoginUserResponseData object.

I'm using the same approach on Android using retrofit and it works fine. I rather don't want to make all fields as optional.

Is there a way to make parser treat empty json {} as nil? Or make LoginUserResponseData as non-optional and it'll just have default values? I know I can create a custom parser for this, but I have tons of requests like this and it'll require too much additional work.

Answer

Arsonik picture Arsonik · Sep 26, 2018

As easy as that !

class LoginUserResponse : Codable {
    var result: String = ""
    var data: LoginUserResponseData?
    var mess: [String] = []

    private enum CodingKeys: String, CodingKey {
        case result, data, mess
    }

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        result = try values.decode(String.self, forKey: .result)
        mess = try values.decode([String].self, forKey: .mess)
        data = try? values.decode(LoginUserResponseData.self, forKey: .data)
    }
}

public class LoginUserResponseData : Codable {
    var userId = "0"
    var name = ""
}

let str = "{\"result\":\"success\",\"data\":{\"userId\":\"10\",\"name\":\"Foo\"},\"mess\":[\"You're logged in\"]}"
let str2 = "{\"result\":\"error\",\"data\":{},\"mess\":[\"Wrong password\"]}"

let decoder = JSONDecoder()
let result = try? decoder.decode(LoginUserResponse.self, from: str.data(using: .utf8)!)
let result2 = try? decoder.decode(LoginUserResponse.self, from: str2.data(using: .utf8)!)
dump(result)
dump(result2)