Swift Codable decode empty json as nil or empty object

二次信任 提交于 2020-08-23 03:05:07

问题


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.


回答1:


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)



回答2:


This is what your implementation of init(from: Decoder) should look like.

Note: You should consider changing LoginUserResponse from a class to a struct, since all it does is store values.

struct LoginUserResponse: Codable {
    var result: String
    var data: LoginUserResponseData?
    var mess: [String]

    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)
        if let d = try? values.decode(LoginUserResponseData.self, forKey: .data) {
            data = d
        }
    }
}



回答3:


My recommendation is to decode result as enum and to initialize data on success.

struct LoginUserResponse : Decodable {

    enum Status : String, Decodable { case success, error }
    private enum CodingKeys: String, CodingKey { case result, data, mess }

    let result : Status
    let data : UserData?
    let mess : [String]

    init(from decoder: Decoder) throws
    {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        result = try values.decode(Status.self, forKey: .result)
        mess = try values.decode([String].self, forKey: .mess)
        switch result {
            case .success: data = try values.decode(UserData.self, forKey: .data)
            case .error: data = nil
        }
    }
}

public struct UserData : Decodable {
    let userId : String
    let name : String
}



回答4:


This is because {} is an empty object but not nil. You have 2 options:

  1. change on server to return null instead of {} for data key
  2. implement custom initializer init(from: Decoder) and handle this case manually



回答5:


Seems it's not possible to treat {} as null, so instead I've created a simple tool to "fix" the API response:

extension String {

    func replaceEmptyJsonWithNull() -> String {
        return replacingOccurrences(of: "{}", with: "null")
    }

}

Other ways are described by @Vitaly Gozhenko and should be used, but I cannot change the server API nor want to write full custom decoder, because this one case.



来源:https://stackoverflow.com/questions/48943510/swift-codable-decode-empty-json-as-nil-or-empty-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!