I\'m trying to find the best way to Encode/Decode an array of structs conforming to a swift protocol using the new JSONDecoder/Encoder in Swift 4.
I made up a little
Drawn from the accepted answer, I ended up with the following code that can be pasted into an Xcode Playground. I used this base to add a codable protocol to my app.
The output looks like this, without the nesting mentioned in the accepted answer.
ORIGINAL:
▿ __lldb_expr_33.Parent
- title: "Parent Struct"
▿ items: 2 elements
▿ __lldb_expr_33.NumberItem
- commonProtocolString: "common string from protocol"
- numberUniqueToThisStruct: 42
▿ __lldb_expr_33.StringItem
- commonProtocolString: "protocol member string"
- stringUniqueToThisStruct: "a random string"
ENCODED TO JSON:
{
"title" : "Parent Struct",
"items" : [
{
"type" : "numberItem",
"numberUniqueToThisStruct" : 42,
"commonProtocolString" : "common string from protocol"
},
{
"type" : "stringItem",
"stringUniqueToThisStruct" : "a random string",
"commonProtocolString" : "protocol member string"
}
]
}
DECODED FROM JSON:
▿ __lldb_expr_33.Parent
- title: "Parent Struct"
▿ items: 2 elements
▿ __lldb_expr_33.NumberItem
- commonProtocolString: "common string from protocol"
- numberUniqueToThisStruct: 42
▿ __lldb_expr_33.StringItem
- commonProtocolString: "protocol member string"
- stringUniqueToThisStruct: "a random string"
Paste into your Xcode project or Playground and customize to your liking:
import Foundation
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 }
}
}
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)
}
}
func testCodableProtocol() {
var items = [Item]()
items.append(NumberItem())
items.append(StringItem())
let parent = Parent(title: "Parent Struct", items: items)
print("ORIGINAL:")
dump(parent)
print("")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(parent)
let jsonString = String(data: jsonData, encoding: .utf8)!
print("ENCODED TO JSON:")
print(jsonString)
print("")
let jsonDecoder = JSONDecoder()
let decoded = try! jsonDecoder.decode(type(of: parent), from: jsonData)
print("DECODED FROM JSON:")
dump(decoded)
print("")
}
testCodableProtocol()