Swift Decodable - 如何解码经过 base64 编码的嵌套 JSON

Oli*_*ain 6 swift codable decodable

我正在尝试解码来自第三方 API 的 JSON 响应,其中包含已进行 base64 编码的嵌套/子 JSON。

人为的示例 JSON

{
   "id": 1234,
   "attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",  
}
Run Code Online (Sandbox Code Playgroud)

PS"eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9"{ 'name': 'some-value' }base64 编码的。

我目前有一些代码可以对此进行解码,但不幸JSONDecoder()的是init,为了这样做,我必须重新实例化一个额外的内部,这并不酷......

人为的示例代码


struct Attributes: Decodable {
    let name: String
}

struct Model: Decodable {

    let id: Int64
    let attributes: Attributes

    private enum CodingKeys: String, CodingKey {
        case id
        case attributes
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.id = try container.decode(Int64.self, forKey: .id)

        let encodedAttributesString = try container.decode(String.self, forKey: .attributes)

        guard let attributesData = Data(base64Encoded: encodedAttributesString) else {
            fatalError()
        }

        // HERE IS WHERE I NEED HELP
        self.attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有办法在不实例化额外的情况下实现解码JSONDecoder

PS:我无法控制响应格式,也无法更改。

Lar*_*rme 2

我发现这个问题很有趣,所以这里有一个可能的解决方案,即为主解码器提供一个额外的解决方案userInfo

extension CodingUserInfoKey {
    static let additionalDecoder = CodingUserInfoKey(rawValue: "AdditionalDecoder")!
}

var decoder = JSONDecoder()
let additionalDecoder = JSONDecoder() //here you can put the same one, you can add different options, same ones, etc.
decoder.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
Run Code Online (Sandbox Code Playgroud)

因为我们使用的主要方法JSONDecoder()func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable并且我想保留它,所以我创建了一个协议:

protocol BasicDecoder {
    func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}

extension JSONDecoder: BasicDecoder {}
Run Code Online (Sandbox Code Playgroud)

我尊重JSONDecoder它(因为它已经尊重了......)

现在,为了玩一点并检查可以做什么,我创建了一个自定义解码器,就像您所说的 XML 解码器一样,它是基本的,并且只是为了好玩(即:不要在家里复制它^ ^):

struct CustomWithJSONSerialization: BasicDecoder {
    func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
        guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { fatalError() }
        return Attributes(name: dict["name"] as! String) as! T
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,init(from:)

guard let attributesData = Data(base64Encoded: encodedAttributesString) else { fatalError() }
guard let additionalDecoder = decoder.userInfo[.additionalDecoder] as? BasicDecoder else { fatalError() }
self.attributes = try additionalDecoder.decode(Attributes.self, from: attributesData)
Run Code Online (Sandbox Code Playgroud)

现在我们就来尝试一下吧!

var decoder = JSONDecoder()
let additionalDecoder = JSONDecoder()
decoder.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]


var decoder2 = JSONDecoder()
let additionalDecoder2 = CustomWithJSONSerialization()
decoder2.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]


let jsonStr = """
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
"""

let jsonData = jsonStr.data(using: .utf8)!

do {
    let value = try decoder.decode(Model.self, from: jsonData)
    print("1: \(value)")
    let value2 = try decoder2.decode(Model.self, from: jsonData)
    print("2: \(value2)")
}
catch {
    print("Error: \(error)")
}
Run Code Online (Sandbox Code Playgroud)

输出:

$> 1: Model(id: 1234, attributes: Quick.Attributes(name: "some-value"))
$> 2: Model(id: 1234, attributes: Quick.Attributes(name: "some-value"))
Run Code Online (Sandbox Code Playgroud)