Skip to content

Instantly share code, notes, and snippets.

@esphynox
Last active October 19, 2018 09:37
Show Gist options
  • Save esphynox/52743ce50f77c451b3babf4f2b2fdac8 to your computer and use it in GitHub Desktop.
Save esphynox/52743ce50f77c451b3babf4f2b2fdac8 to your computer and use it in GitHub Desktop.
Decoding heterogenous arrays with base protocol in Swift 4
import Foundation
protocol Item: Codable {
static var type: ItemType { get }
var commonProtocolString: String { get }
}
enum ItemType: String, Codable {
case numberItem
case stringItem
var metatype: Item.Type {
switch self {
case .numberItem: return NumberItem.self
case .stringItem: return StringItem.self
}
}
}
struct NumberItem: Item {
static var type = ItemType.numberItem
let commonProtocolString = "common string from protocol"
let numberUniqueToThisStruct = 42
}
struct StringItem: Item {
static var type = ItemType.stringItem
let commonProtocolString = "protocol member string"
let stringUniqueToThisStruct = "a random string"
}
struct AnyItem: Codable {
var item: Item
init(_ item: Item) {
self.item = item
}
private enum CodingKeys : CodingKey {
case type
case item
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: item).type, forKey: .type)
try item.encode(to: encoder)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ItemType.self, forKey: .type)
self.item = try type.metatype.init(from: decoder)
}
}
struct Parent: Codable {
let title: String
let items: [Item]
init(title: String, items: [Item]) {
self.title = title
self.items = items
}
enum CodingKeys: String, CodingKey {
case title
case items
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(items.map({ AnyItem($0) }), forKey: .items)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
items = try container.decode([AnyItem].self, forKey: .items).map { $0.item }
}
}
func testEncoding(_ parent: Parent) -> Data {
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(parent)
let jsonString = String(data: jsonData, encoding: .utf8)!
return jsonData
}
func testDecoding(_ jsonData: Data) -> Parent {
let jsonDecoder = JSONDecoder()
let decoded = try! jsonDecoder.decode(type(of: parent), from: jsonData)
return decoded
}
let parent = Parent(title: "Parent Struct", items: [StringItem(), NumberItem()])
let jsonData = testEncoding(parent)
let jsonString = String(data: jsonData, encoding: .utf8)!
print("Encoded object to JSON:\n\(jsonString)")
let parentDecoded = testDecoding(jsonData)
print("Decoded object from JSON:\n\(parentDecoded)")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment