Swift Decodable, Endpoint возвращает совершенно разные типы - PullRequest
0 голосов
/ 10 ноября 2019

С API, с которым я работаю, у меня есть случай, когда 1 конечная точка API может возвращать совершенно разные ответы, в зависимости от того, был ли вызов успешным или нет.
В случае успеха , APIКонечная точка возвращает массив запрашиваемых объектов в корне, что-то вроде этого:

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

, который я обычно декодирую с помощью try JSONDecoder().decode([Object].self, from: data)

В случае ошибки , API Endpoint возвращает что-то совершенно другое, выглядит так:

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

и декодирование с помощью try JSONDecoder().decode([Object].self, from: data) обычно завершается неудачей.

Теперь, мой вопрос ,Есть ли способ, чтобы декодировать ключи ответа на ошибку, в этом виде (я бы сказал, не так нормально с архитектурой API), БЕЗ создания-то, что я называю- множественное число объект с именем Objects, который будет иметь необязательные свойства error, message, status, и, например, objects.
. Мне пришло в голову кое-что расширить массив where Element == Object и каким-то образом попытаться декодировать error, message, status, но я бью Conformance of 'Array<Element>' to protocol 'Decodable' was already stated in the type's module 'Swift'. Может быть, это даже невозможно сделать таким образом, поэтому любые другие, даже совершенно другие, предложения будут очень кстати.

Ответы [ 4 ]

1 голос
/ 10 ноября 2019

Вы можете попытаться декодировать [Object] и, если это не получится, декодировать другую структуру с вашими ключами ошибки.

0 голосов
/ 11 ноября 2019

Я предлагаю декодировать корневой объект JSON как enum со связанными типами

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))
        }
    }
}

и использовать его

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)
}

Хорошей практикой является отлов только определенных .typeMismatch ошибка и мгновенно передать другие ошибки вызывающей стороне.

0 голосов
/ 11 ноября 2019

Используйте блок do-catch, чтобы попытаться декодировать один тип, а в случае неудачи попробуйте другой вариант. Мне очень нравится использовать enum для обработки результата ...

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)
}
0 голосов
/ 11 ноября 2019

Введите "абстрактную" структуру, которая является получателем вызова декодирования, и пусть эта структура декодирует правильный тип и возвращает Result объект

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))
        }
    }
}

, и затем его можно использовать, например, используязакрытие

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)
    }
}
...