首页 > 解决方案 > 允许 JSONDecoder() 接受空数组作为空字典

问题描述

鉴于此结构:

public struct Error: Codable {
    public let code: String
    public let message: String
    public let params: [String: String]?
}

而这个 JSON:

[
  {
    "message" : "The requested user could not be found.",
    "params" : [],
    "code" : "requested_user_not_found"
  }
]

有没有办法使用 Swift 中的 JSONDecoder() 类对此进行解码?params键应该是字典,但由于生成此 JSON 的外部 API 的实现方式(在 PHP 中),空字典在 JSON 中呈现为空数组。

目前,尝试将提供的 JSON 解码为提供的结构的实例会导致抛出错误。

标签: iosjsonswift

解决方案


首先,PHP 不需要这样做,所以如果可能的话,应该更正 PHP。在 PHP 中表达“空对象”的方式是new \stdClass(). 弹性有一个很好的解释。

也就是说,如果您无法更正服务器,则可以在客户端进行修复。这里的许多答案都是基于尝试解码该值,如果失败则假设它是一个空数组。这行得通,但这意味着意外的 JSON 不会产生好的错误。相反,我会将这个问题提取到一个函数中:

/// Some PHP developers emit [] to indicate empty object rather than using stdClass().
/// This returns a default value in that case.
extension KeyedDecodingContainer {
    func decodePHPObject<T>(_ type: T.Type, forKey key: Key, defaultValue: T) throws -> T
        where T : Decodable
    {
        // Sadly neither Void nor Never conform to Decodable, so we have to pick a random type here, String.
        // The nicest would be to decode EmptyCollection, but that also doesn't conform to Decodable.
        if let emptyArray = try? decode([String].self, forKey: key), emptyArray.isEmpty {
            return defaultValue
        }

        return try decode(T.self, forKey: key)
    }

    // Special case the common dictionary situation.
    func decodePHPObject<K, V>(_ type: [K: V].Type, forKey key: Key) throws -> [K: V]
        where K: Codable, V: Codable
    {
        return try decodePHPObject([K:V].self, forKey: key, defaultValue: [:])
    }
}

这提供了.decodePHPObject(_:forKey:)一种您可以在自定义解码器中使用的方法。

public struct ErrorValue: Codable {
    public let code: String
    public let message: String
    public let params: [String: String]

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.code = try container.decode(String.self, forKey: .code)
        self.message = try container.decode(String.self, forKey: .message)

        // And use our new decodePHPObject.
        self.params = try container.decodePHPObject([String: String].self, forKey: .params)
    }
}

(我已将其重命名ErrorValue以消除与 stdlib 类型的冲突Error,并且我已将其设为params非可选,因为您通常不应该有可选的集合,除非“空”的处理方式与 nil 不同。)


推荐阅读