我正在尝试解码来自第三方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)
}
}
代码的其余部分将保持不变。
您可以创建一个解码器作为Model
的static
属性,对其进行一次配置,并将其用于所有Model
解码需求,包括外部和内部。
不请自来的想法:老实说,我只建议你这样做,如果你看到额外的JSONDecoder的分配造成了CPU时间的可衡量的损失或疯狂的堆增长……它们不是重量级对象,小于128字节,除非有一些我不理解的把戏(这很常见,但tbh(:
let decoder = JSONDecoder()
malloc_size(Unmanaged.passRetained(decoder).toOpaque()) // 128