如何解决Swift Decodable-如何解码已被base64编码的嵌套JSON
我正在尝试从第三方API解码JSON响应,该API包含已被base64编码的嵌套/子JSON。
设计示例JSON
{
"id": 1234,"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",}
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)
}
}
有没有办法实现解码而又不增加附加的JSONDecoder
?
PS:我无法控制响应格式,因此无法更改。
解决方法
如果attributes
仅包含一个键值对,这是简单的解决方案。
它直接将base64编码的字符串解码为Data
(这可以通过.base64
数据解码策略来实现),并使用传统的JSONSerialization
进行反序列化。该值已分配给name
结构中的成员Model
。
如果无法解码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 Decoder一样,它是基本的,而且只是为了好玩(即:不要复制它)在家^^):
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,}
"""
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"))
,
阅读this interesting post之后,我想出了一个可重用的解决方案。
您可以创建一个新的NestedJSONDecodable
协议,该协议还将JSONDecoder
放入其初始化程序中:
protocol NestedJSONDecodable: Decodable {
init(from decoder: Decoder,using nestedDecoder: JSONDecoder) throws
}
实施解码器提取技术(来自上述文章)以及用于解码decode(_:from:)
类型的新NestedJSONDecodable
函数:
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)
}
}
其余代码将保持不变。
,您可以将单个解码器创建为static
的{{1}}属性,对其进行一次配置,然后将其用于内部和内部的所有Model
解码需求。
主动提出的想法: 老实说,我只建议这样做,如果您发现由于分配额外的JSONDecoder而导致CPU时间明显减少或疯狂堆增长……它们不是重量级的对象,少于128个字节,除非有一些我不明白的技巧(虽然在tbh中很常见):
Model
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。