Skip to content

Instantly share code, notes, and snippets.

@nicklockwood
Last active September 18, 2019 00:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicklockwood/3f99995a9273b9e3746ec1579276efd9 to your computer and use it in GitHub Desktop.
Save nicklockwood/3f99995a9273b9e3746ec1579276efd9 to your computer and use it in GitHub Desktop.
POC for serializing heterogeneous array of classes using Codable in Swift
import Foundation
enum FooType: String, Codable {
case bar, baz
}
protocol Foo: AnyObject, Codable {
var type: FooType { get }
}
extension Foo {
func encode(to container: inout UnkeyedEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyFoo: Codable {
let value: Foo
init(_ value: Foo) {
self.value = value
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
switch try container.decode(FooType.self) {
case .bar:
self.init(try container.decode(Bar.self))
case .baz:
self.init(try container.decode(Baz.self))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(value.type)
try value.encode(to: &container)
}
}
class Bar: Foo {
let type = FooType.bar
var bar: Int
init(bar: Int) {
self.bar = bar
}
private enum CodingKeys: CodingKey {
case bar
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
bar = try container.decode(Int.self, forKey: .bar)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(bar, forKey: .bar)
}
}
class Baz: Foo {
let type = FooType.baz
var baz: String
init(baz: String) {
self.baz = baz
}
private enum CodingKeys: CodingKey {
case baz
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
baz = try container.decode(String.self, forKey: .baz)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(baz, forKey: .baz)
}
}
let coder = JSONEncoder()
let decoder = JSONDecoder()
let values: [Foo] = [
Bar(bar: 5),
Baz(baz: "Hello")
]
let typeErasedValues = values.map(AnyFoo.init)
let data = coder.encode(typeErasedValues)
let decodedErasedValues = decoder.decode([AnyFoo].self, from: data)
let decodedValues = decodedErasedValues.map { $0.value }
print((decodedValues[0] as! Bar).bar) // 5
print((decodedValues[1] as! Baz).baz) // Hello
@nicklockwood
Copy link
Author

For comparison, here is the struct version: https://gist.github.com/nicklockwood/833fabacbc4b2d11ae7c7d4752b8fd18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment