使用 JSON 解码器用空对象解码 JSON

Decode JSON with empty object using JSONDecoder

我想使用 JSONDecoder 解码 JSON。它按预期工作,但是对于内部对象为空的 JSON JSONDecoder 会抛出错误 The data couldn’t be read because it is missing.

示例 JSON 错误:

{
    "header": {
        "code": 1053,
        "message": "Incorrect information."
    },
    "body": {}
}

样本 JSON 成功:

{
    "header": {
        "code": 1053
        "message": "Nice information."
    },
    "body": {
        "client_id": 12345
    }
}

成功JSON,轻松破译。但是在错误 JSON 上,它抛出错误。

这是我使用的代码

struct ApiResponse: Decodable {

    let header: Header
    let body: Body

    struct Header: Decodable {
        let responseCode: Int
        let message: String
    }

    struct Body: Decodable {
        let clientId: Int
    }
}

let decoder: JSONDecoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedResponse = try decoder.decode(ApiResponse.self, from: data)

您始终可以实现自己的解码功能,让您可以做任何您想做的事情,但解决这个问题的快速方法只是将无法保证 return 的任何内容标记为可选。

请记住,如果密钥是由服务器 return 编辑的,那么它必须正确解码。通常我会建议使 Body 可选..但是在这里它不起作用.

在这种情况下,您需要执行以下操作:

struct ApiResponse: Decodable {

    let header: Header
    let body: Body // key is being returned so we keep it as part of the response

    struct Header: Decodable {
        let code: Int // match your variables with the JSON being returned
        let message: String
    }

    struct Body: Decodable {
        let clientId: Int? // sometimes nothing comes inside the body dict, so make its internals optional
    }
}

编辑:

此外,正如 Leo 在评论中指出的那样,您还犯了一个愚蠢的错误,即您的变量实际上没有与响应匹配。请注意,您的 JSON 将 code 作为键,而您的 Header 对象正在寻找 responseCode 我已经编辑了我的原始回复以进行此更改。

您可以扩展 KeyedDecodingContainer 以使用协议

将空字典视为 nil
public protocol EmptyDictionaryRepresentable {
    associatedtype CodingKeys : RawRepresentable where CodingKeys.RawValue == String
    associatedtype CodingKeyType: CodingKey = Self.CodingKeys
}

extension KeyedDecodingContainer {
    public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T?
        where T : Decodable & EmptyDictionaryRepresentable
    {
        guard contains(key) else { return nil }
        let container = try nestedContainer(keyedBy: type.CodingKeyType.self, forKey: key)
        return container.allKeys.isEmpty ? nil : try decode(T.self, forKey: key)
    }
}

要使用它,请采用协议并将受影响的 属性 声明为可选的

struct ApiResponse: Decodable {
    let header: Header
    let body: Body?
}

struct Body: Decodable, EmptyDictionaryRepresentable {
    enum CodingKeys : String, CodingKey { case clientId = "client_id" }

    let clientId: Int
}

警告:此解决方案不适用于 .convertFromSnakeCase 策略

注意:考虑 Header

中的键 - 结构成员名称不匹配

声明 clientId 为可选 属性。因为在你的错误 JSON client_id 不存在。有关详细信息,请阅读 Article.