Swift Decodable, Endpoint returns completely different types

人盡茶涼 提交于 2020-02-06 08:03:56

问题


With API I'm working with, I have a case where 1 API Endpoint can return completely different responses, based on if the call was successful or not.
In case of success, API Endpoint returns an Array of requested objects, in the root, something like this:

[
    {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    },
    {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    },
    ...
]

which I'm normally decoding with try JSONDecoder().decode([Object].self, from: data)

In case of an error, API Endpoint returns something completely different, looks like this:

{
    "error": "value1",
    "message": "value2",
    "status": "value3"
}

and decoding with try JSONDecoder().decode([Object].self, from: data) normally fails.

Now, my question is, is there a way, to decode error response keys, in this kind of (I would say not so normally architectured API), WITHOUT creating a -what I call- plural object named Objects that would have optional properties error, message, status, and for example objects.
My thinking got somewhere to extending Array where Element == Object and somehow trying to decode error, message, status, but I'm hitting Conformance of 'Array<Element>' to protocol 'Decodable' was already stated in the type's module 'Swift'. Maybe it's not even possible to do it that way, so any other, even completely different, suggestion would be very welcome.


回答1:


You can try to decode [Object] and if that fails, decode another struct with your error keys.




回答2:


Utilise a do-catch block to allow you try decoding one type, and if that fails try the other option. I quite like to use an enum to handle the result...

struct Opt1: Codable {
   let key1, key2, key3: String
}

struct Opt2: Codable {
   let error, message, status: String
}

enum Output {
   case success([Opt1])
   case failure(Opt2)
}

let decoder = JSONDecoder()
let data = json.data(using: .utf8)!
var output: Output

do {
   let opt1Array = try decoder.decode([Opt1].self, from: data)
   output = .success(opt1Array)
} catch {
   let opt2 = try decoder.decode(Opt2.self, from: data)
   output = .failure(opt2)
}



回答3:


Introduce an "abstract" struct that is the receiver of the decode call and let that struct decode the correct type and return a Result object

enum ApiErrorEnum: Error {
    case error(ApiError)
}

struct ResponseHandler: Decodable {
    let result: Result<[ApiResult], ApiErrorEnum>

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()

        do {
            let values = try container.decode([ApiResult].self)
            result = .success(values)
        } catch {
            let apiError = try container.decode(ApiError.self)
            result = .failure(.error(apiError))
        }
    }
}

and it could then be used for instance using a closure

func decodeApi(_ data: Data, completion: @escaping (Result<[ApiResult], ApiErrorEnum>?, Error?) -> ()) {
    do {
        let decoded = try JSONDecoder().decode(ResponseHandler.self, from: data)
        completion(decoded.result, nil)
    } catch {
        completion(nil, error)
    }
}



回答4:


My suggestion is to decode the root object of the JSON as enum with associated types

struct Item : Decodable {
    let key1, key2, key3 : String
}

struct ResponseError  : Decodable {
    let error, message, status : String
}

enum Response : Decodable {
    case success([Item]), failure(ResponseError)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = .success(try container.decode([Item].self))
        } catch DecodingError.typeMismatch {
            self = .failure(try container.decode(ResponseError.self))
        }
    }
}

and use it

do {
    let result = try JSONDecoder().decode(Response.self, from: data)
    switch result {
        case .success(let items): print(items)
        case .failure(let error): print(error.message)
    }
} catch {
    print(error)
}

It's good practice to catch only the specific .typeMismatch error and hand over other errors instantly to the caller.



来源:https://stackoverflow.com/questions/58792754/swift-decodable-endpoint-returns-completely-different-types

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