Кодируемые и необязательные JSON поля - PullRequest
0 голосов
/ 07 мая 2020

Существует множество API-интерфейсов, которые отвечают соответствующим кодом состояния HTTP, если что-то пошло не так, но есть также API-интерфейсы, которые всегда отвечают кодом состояния 200, описывающим ошибку в определенных полях c JSON. Это как раз мой случай. Итак, я создал протокол BasicResponse, который требует, чтобы любая структура Codable имела эти обязательные поля:

protocol BasicResponse: Codable {
    var response: String { get }
    var message: String? { get }
}

Моя структура Codable теперь выглядит так:

struct GetTicketsResponse: Codable, BasicResponse {
    var response: String
    var message: String?

    var tickets: [Ticket]
}

И Я также создал своего рода промежуточный декодер для выявления ошибки на ранних стадиях:

class AdvancedDecoder {

    func decode<T: BasicResponse>(_ type: T.Type, from data: Data) throws -> T {
        let result = try JSONDecoder().decode(T.self, from: data)

        // Check if session is valid
        if (result.message == "Session invalid") { throw AdvancedError.invalidSession }

        return result
    }

}

Но я заметил проблему с этим подходом. Если что-то не удается на сервере, он возвращает JSON, которые имеют эти поля response и message, но не имеют поля tickets, поэтому декодирование ответа всегда терпит неудачу. Я мог бы отметить массив tickets как необязательный тип var tickets: [Ticket]?, но мне это кажется очень странным решением. Вот два JSON для справки:

// If everything went smoothly
{
    "response" : "OK",

    "tickets" : [...]
}

// If something went wrong
{
    "response" : "ERROR",
    "message" : "Session invalid"
}

Итак, как мне справиться с подобными сценариями ios? Бьюсь об заклад, есть способ получше решить эту проблему. Заранее спасибо :)

Ответы [ 2 ]

0 голосов
/ 07 мая 2020

На мой взгляд, перечисление со связанными типами является наиболее эффективным решением

enum Response : Decodable {

    case success([Ticket])
    case failure(String)

    private enum CodingKeys : String, CodingKey { case response, message, tickets}

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let response = try container.decode(String.self, forKey: .response)
        switch response {
            case "OK":
                let ticketData = try container.decode([Ticket].self, forKey: .tickets)
                self = .success(ticketData)
            case "ERROR":
                let errorMessage = try container.decode(String.self, forKey: .message)
                self = .failure(errorMessage)
            default:
                throw DecodingError.dataCorruptedError(forKey: .response, in: container, debugDescription: "Invalid response string")
        }
    }
}

И используйте его

do {
    let result = try JSONDecoder().decode(Response.self, from: data)
    switch result {
        case .success(let tickets): print(tickets)
        case .failure(let errorMessage): print(errorMessage)
    }
} catch {
    print(error)
}
0 голосов
/ 07 мая 2020

Вы можете создать две разные Response структуры для успеха и неудачи. Если декодирование не удается, попробуйте выполнить декодирование с ошибкой.

struct SuccessResponse: Codable {
    var tickets: [...]
}

struct FailureResponse: Codable {
    var response, message: String
}

func decodingFunction(data: Data) {
    do {
        let response = try JSONDecoder().decode(SuccessResponse.self, from: data)
        let tickets = response.tickets
        //...
    } catch {
        do {

            let failureResponse = try JSONDecoder().decode(FailureResponse.self, from: data)
            let message = failureResponse.message
            //...
        } catch {
            print(error.localizedDescription)
        }
    }
}
...