Swift 可解码 - 如何解码已进行 base64 编码的嵌套 JSON
Swift Decodable - How to decode nested JSON that has been base64 encoded
我正在尝试解码来自第三方 API 的 JSON 响应,其中包含经过 base64 编码的 nested/child JSON。
人为的例子JSON
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
PS "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9"
是 { 'name': 'some-value' }
base64 编码。
我目前有一些代码可以对此进行解码,但不幸的是,我必须在 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
的情况下实现解码?
PS: 我无法控制响应格式,无法更改。
您可以创建一个解码器作为 Model
的 static
属性,配置一次,然后将其用于所有 Model
解码需求,无论是外部的和内部。
不请自来的想法:
老实说,如果您看到可测量的 CPU 时间损失或分配额外的 JSONDecoder 导致堆疯狂增长,我只会建议您这样做……它们不是重量级对象,小于 128 字节,除非我有一些诡计不明白(虽然说实话这很常见):
let decoder = JSONDecoder()
malloc_size(Unmanaged.passRetained(decoder).toOpaque()) // 128
阅读 this interesting post 后,我想到了一个可重复使用的解决方案。
您可以创建一个新的 NestedJSONDecodable
协议,它的初始化程序中也包含 JSONDecoder
:
protocol NestedJSONDecodable: Decodable {
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws
}
实施解码器提取技术(来自上述 post)以及用于解码 NestedJSONDecodable
类型的新 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)
}
}
其余代码将保持不变。
如果 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"))
我正在尝试解码来自第三方 API 的 JSON 响应,其中包含经过 base64 编码的 nested/child JSON。
人为的例子JSON
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
PS "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9"
是 { 'name': 'some-value' }
base64 编码。
我目前有一些代码可以对此进行解码,但不幸的是,我必须在 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
的情况下实现解码?
PS: 我无法控制响应格式,无法更改。
您可以创建一个解码器作为 Model
的 static
属性,配置一次,然后将其用于所有 Model
解码需求,无论是外部的和内部。
不请自来的想法: 老实说,如果您看到可测量的 CPU 时间损失或分配额外的 JSONDecoder 导致堆疯狂增长,我只会建议您这样做……它们不是重量级对象,小于 128 字节,除非我有一些诡计不明白(虽然说实话这很常见):
let decoder = JSONDecoder()
malloc_size(Unmanaged.passRetained(decoder).toOpaque()) // 128
阅读 this interesting post 后,我想到了一个可重复使用的解决方案。
您可以创建一个新的 NestedJSONDecodable
协议,它的初始化程序中也包含 JSONDecoder
:
protocol NestedJSONDecodable: Decodable {
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws
}
实施解码器提取技术(来自上述 post)以及用于解码 NestedJSONDecodable
类型的新 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)
}
}
其余代码将保持不变。
如果 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"))