Swift DeHow解码已经base64编码的嵌套JSON



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

设计示例JSON

{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",  
}

PS"eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9"是基于CCD_。

我目前有一些代码可以解码,但不幸的是,我必须在init内部重新安装一个额外的JSONDecoder()才能解码,这并不酷。。。

设计示例代码


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

是否有任何方法可以在不设置附加JSONDecoder的情况下实现解码?

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

如果attributes只包含一个键值对,这就是简单的解决方案。

它将base64编码的字符串直接解码为Data——这在.base64数据解码策略下是可能的——并使用传统的JSONSerialization对其进行反序列化。该值被分配给Model结构中的成员name

如果base64编码的字符串不能被解码,则DecodingError将被抛出

let jsonString = """
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
"""
struct Model: Decodable {

let id: Int64
let name: String

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

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let attributeData = try container.decode(Data.self, forKey: .attributes)
guard let attributes = try JSONSerialization.jsonObject(with: attributeData) as? [String:String],
let attributeName = attributes["name"] else { throw DecodingError.dataCorruptedError(forKey: .attributes, in: container, debugDescription: "Attributes isn't eiter a dicionary or has no key name") }
self.name = attributeName
}
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
let result = try decoder.decode(Model.self, from: data)
print(result)
} catch {
print(error)
}

我觉得这个问题很有趣,所以这里有一个可能的解决方案,那就是在主解码器的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]

因为我们在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 {}

我让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
}
}

因此,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)

让我们现在就试试吧!

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

输出:

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

在阅读了这篇有趣的文章后,我提出了一个可重用的解决方案。

您可以创建一个新的NestedJSONDecodable协议,该协议的初始值设定项中也包含JSONDecoder

protocol NestedJSONDecodable: Decodable {
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws
}

实现解码器提取技术(来自上述帖子(以及用于解码{ 'name': 'some-value' }1类型的新decode(_:from:)功能:

protocol DecoderExtractable {
func decoder(for data: Data) throws -> Decoder
}
extension JSONDecoder: DecoderExtractable {
struct DecoderExtractor: Decodable {
let decoder: Decoder

init(from decoder: Decoder) throws {
self.decoder = decoder
}
}

func decoder(for data: Data) throws -> Decoder {
return try decode(DecoderExtractor.self, from: data).decoder
}

func decode<T: NestedJSONDecodable>(_ type: T.Type, from data: Data) throws -> T {
return try T(from: try decoder(for: data), using: self)
}
}

并更改您的Model结构以符合NestedJSONDecodable协议,而不是Decodable:

struct Model: NestedJSONDecodable {
let id: Int64
let attributes: Attributes
private enum CodingKeys: String, CodingKey {
case id
case attributes
}
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let attributesData = try container.decode(Data.self, forKey: .attributes)

self.attributes = try nestedDecoder.decode(Attributes.self, from: attributesData)
}
}

代码的其余部分将保持不变。

您可以创建一个解码器作为Modelstatic属性,对其进行一次配置,并将其用于所有Model解码需求,包括外部和内部。

不请自来的想法:老实说,我只建议你这样做,如果你看到额外的JSONDecoder的分配造成了CPU时间的可衡量的损失或疯狂的堆增长……它们不是重量级对象,小于128字节,除非有一些我不理解的把戏(这很常见,但tbh(:

let decoder = JSONDecoder()
malloc_size(Unmanaged.passRetained(decoder).toOpaque()) // 128

相关内容

  • 没有找到相关文章

最新更新