【问题标题】:Codable and optional JSON fields可编码和可选的 JSON 字段
【发布时间】:2020-05-07 09:21:38
【问题描述】:

如果出现问题,有许多 API 会以适当的 HTTP 状态代码进行响应,但也有一些 API 总是以 200 状态代码来响应特定 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
    }

}

但我注意到这种方法存在问题。如果服务器上出现故障,它将返回具有 responsemessage 字段但没有 tickets 字段的 JSON,因此解码响应总是失败。我可以将tickets 数组标记为可选类型var tickets: [Ticket]?,但这对我来说似乎是一个非常奇怪的解决方法。这里有两个 JSON 供参考:

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

    "tickets" : [...]
}

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

那么我该如何处理这种情况呢?我敢打赌,有更好的解决方案。在此先感谢:)

【问题讨论】:

  • var tickets: [Ticket]? 是一个解决方案
  • tickets 设为可选项有什么问题?这就是这种情况的处理方式,直到成功和失败的情况完全不同。
  • @PGDev 对我来说似乎有点奇怪,因为它要么有 tickets 字段,要么有错误字段。没有其他方案,因此将tickets 设为可选类型只是为了防止解码错误,对于这种简单的情况来说似乎过于复杂了。

标签: json swift codable


【解决方案1】:

在我看来,具有关联类型的枚举是最有效的解决方案

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

【讨论】:

    【解决方案2】:

    您可以为成功和失败创建两个不同的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)
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-07-20
      • 2022-06-10
      • 2014-04-12
      • 2015-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-04
      • 2013-03-27
      相关资源
      最近更新 更多