如何根据'type'为多个结构手动实现Codable?
How to manually implement Codable for multiple structs according to 'type'?
考虑以下 json:
{
"from": "Guille",
"text": "Look what I just found!",
"attachments": [
{
"type": "image",
"payload": {
"url": "http://via.placeholder.com/640x480",
"width": 640,
"height": 480
}
},
{
"type": "audio",
"payload": {
"title": "Never Gonna Give You Up",
"url": "https://audio.com/NeverGonnaGiveYouUp.mp3",
"shouldAutoplay": true,
}
}
]
}
以及以下 Swift 结构:
struct ImageAttachment: Codable {
let url: URL
let width: Int
let height: Int
}
struct AudioAttachment: Codable {
let title: String
let url: URL
let shouldAutoplay: Bool
}
enum Attachment {
case image(ImageAttachment)
case audio(AudioAttachment)
case unsupported
}
extension Attachment: Codable {
private enum CodingKeys: String, CodingKey {
case type
case payload
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "image":
let payload = try container.decode(ImageAttachment.self, forKey: .payload)
self = .image(payload)
case "audio":
let payload = try container.decode(AudioAttachment.self, forKey: .payload)
self = .audio(payload)
default:
self = .unsupported
}
}
...
}
如果 'payload' 关键参数是扁平的(也就是没有 'payload'),我将如何处理类似的用例,例如:
{
"type": "image",
"url": "http://via.placeholder.com/640x480",
"width": 640,
"height": 480
}
{
"type": "audio",
"title": "Never Gonna Give You Up",
"url": "https://audio.com/NeverGonnaGiveYouUp.mp3",
"shouldAutoplay": true,
}
我不知道如何为平面案例正确实现初始化解码器,因为同时保留附件结构并允许未来的灵活性(添加更多类型的附件)。
你只需要做一个小改动
extension Attachment: Codable {
private enum CodingKeys: String, CodingKey {
case type
case payload
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
// The attachment data is nested if it has the `payload` key
let isNested = container.allKeys.contains(.payload)
switch type {
case "image":
// If the attachment data is nested inside the `payload` property, decode
// it from that property. Otherwise, decode it from the current decoder
let payload = try isNested ? container.decode(ImageAttachment.self, forKey: .payload) : ImageAttachment(from: decoder)
self = .image(payload)
case "audio":
// Same as image attachment above
let payload = try isNested ? container.decode(AudioAttachment.self, forKey: .payload) : AudioAttachment(from: decoder)
self = .audio(payload)
default:
self = .unsupported
}
}
}
考虑以下 json:
{
"from": "Guille",
"text": "Look what I just found!",
"attachments": [
{
"type": "image",
"payload": {
"url": "http://via.placeholder.com/640x480",
"width": 640,
"height": 480
}
},
{
"type": "audio",
"payload": {
"title": "Never Gonna Give You Up",
"url": "https://audio.com/NeverGonnaGiveYouUp.mp3",
"shouldAutoplay": true,
}
}
]
}
以及以下 Swift 结构:
struct ImageAttachment: Codable {
let url: URL
let width: Int
let height: Int
}
struct AudioAttachment: Codable {
let title: String
let url: URL
let shouldAutoplay: Bool
}
enum Attachment {
case image(ImageAttachment)
case audio(AudioAttachment)
case unsupported
}
extension Attachment: Codable {
private enum CodingKeys: String, CodingKey {
case type
case payload
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "image":
let payload = try container.decode(ImageAttachment.self, forKey: .payload)
self = .image(payload)
case "audio":
let payload = try container.decode(AudioAttachment.self, forKey: .payload)
self = .audio(payload)
default:
self = .unsupported
}
}
...
}
如果 'payload' 关键参数是扁平的(也就是没有 'payload'),我将如何处理类似的用例,例如:
{
"type": "image",
"url": "http://via.placeholder.com/640x480",
"width": 640,
"height": 480
}
{
"type": "audio",
"title": "Never Gonna Give You Up",
"url": "https://audio.com/NeverGonnaGiveYouUp.mp3",
"shouldAutoplay": true,
}
我不知道如何为平面案例正确实现初始化解码器,因为同时保留附件结构并允许未来的灵活性(添加更多类型的附件)。
你只需要做一个小改动
extension Attachment: Codable {
private enum CodingKeys: String, CodingKey {
case type
case payload
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
// The attachment data is nested if it has the `payload` key
let isNested = container.allKeys.contains(.payload)
switch type {
case "image":
// If the attachment data is nested inside the `payload` property, decode
// it from that property. Otherwise, decode it from the current decoder
let payload = try isNested ? container.decode(ImageAttachment.self, forKey: .payload) : ImageAttachment(from: decoder)
self = .image(payload)
case "audio":
// Same as image attachment above
let payload = try isNested ? container.decode(AudioAttachment.self, forKey: .payload) : AudioAttachment(from: decoder)
self = .audio(payload)
default:
self = .unsupported
}
}
}