Reputation: 2555
Consider the following 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,
}
}
]
}
And the following Swift structure:
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
}
}
...
}
How would I go about handling the similar use case if the 'payload' key params were flat (aka without 'payload') like:
{
"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,
}
I can't figure out how to implement the init decoder properly for the flat case since while preserving the Attachment structures and allowing for future flexibility (of adding more types of attachments).
Upvotes: 2
Views: 253
Reputation: 93151
You only need to make a small change
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
}
}
}
Upvotes: 1