Skip to content

Instantly share code, notes, and snippets.

@saiday
Last active April 27, 2020 16:26
Show Gist options
  • Save saiday/c94f0f18f4bbb0b10775a6d3079a4f97 to your computer and use it in GitHub Desktop.
Save saiday/c94f0f18f4bbb0b10775a6d3079a4f97 to your computer and use it in GitHub Desktop.
import Foundation
let json = """
{
"results":[
{
"blah":"blah",
"nested_object":{
"type":"a",
"id":69,
"aVar":"aaa"
}
},
{
"blah":"blah",
"nested_object":{
"type":"b",
"id":42,
"bVar":"bbb"
}
}
]
}
"""
protocol HeterogeneousClassFamily: Decodable {
associatedtype BaseType: Decodable
static var discriminator: Discriminator { get }
static func decodeHeterogeneousItem<CodingKey>(container: KeyedDecodingContainer<CodingKey>, key: CodingKey) throws -> BaseType
}
enum Discriminator: String, CodingKey {
case type
}
enum SuperClassFamily: String, HeterogeneousClassFamily {
typealias BaseType = Superclass
case a
case b
static var discriminator: Discriminator { return .type }
static func decodeHeterogeneousItem<CodingKey>(container: KeyedDecodingContainer<CodingKey>, key: CodingKey) throws -> Superclass {
let keyContainer = try container.nestedContainer(keyedBy: Discriminator.self, forKey: key)
let family = try keyContainer.decode(SuperClassFamily.self, forKey: SuperClassFamily.discriminator)
switch family {
case .a:
return try container.decode(SubclassA.self, forKey: key)
case .b:
return try container.decode(SubclassB.self, forKey: key)
}
}
}
class Superclass: Codable {
var id: Int
var type: String
}
class SubclassA: Superclass {
var aVar: String
enum CodingKeys: String, CodingKey {
case id
case type
case aVar
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.aVar = try container.decode(String.self, forKey: .aVar)
try super.init(from: decoder)
self.id = try container.decode(Int.self, forKey: .id)
self.type = try container.decode(String.self, forKey: .type)
}
}
class SubclassB: Superclass {
var bVar: String
enum CodingKeys: String, CodingKey {
case id
case type
case bVar
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.bVar = try container.decode(String.self, forKey: .bVar)
try super.init(from: decoder)
self.id = try container.decode(Int.self, forKey: .id)
self.type = try container.decode(String.self, forKey: .type)
}
}
class HeterogeneousContainerEntity: Codable {
enum CodingKeys: String, CodingKey {
case nestedObject = "nested_object"
}
var nestedObject: Superclass?
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nestedObject = try SuperClassFamily.decodeHeterogeneousItem(container: container, key: .nestedObject)
}
}
struct Page<T: Codable>: Codable {
enum CodingKeys: String, CodingKey {
case results
}
var results: [T]
}
// example
let decoder = JSONDecoder()
let decodedRoot = try decoder.decode(Page<HeterogeneousContainerEntity>.self, from: json.data(using: .utf8)!)
let isA = decodedRoot.results[0].nestedObject is SubclassA // true
let isB = decodedRoot.results[1].nestedObject is SubclassB // true
// Since Codable doesn't have "built in" support for polymorphism, this ideally approach won't work
// ref: https://bugs.swift.org/browse/SR-5331
/*
class HeterogeneousContainerEntityEx: Codable {
enum CodingKeys: String, CodingKey {
case nestedObject = "nested_object"
}
var nestedObject: Superclass?
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nestedObject = try container.decodeHeterogeneousItem(family: SuperClassFamilyEx.self, forKey: .nestedObject)
}
}
protocol HeterogeneousClassFamilyEx: Decodable {
associatedtype BaseType: Decodable
static var discriminator: Discriminator { get }
func getType() -> BaseType.Type
}
enum SuperClassFamilyEx: String, HeterogeneousClassFamilyEx {
typealias BaseType = Superclass
case a
case b
static var discriminator: Discriminator { return .type }
func getType() -> Superclass.Type {
switch self {
case .a:
return SubclassA.self
case .b:
return SubclassB.self
}
}
}
extension KeyedDecodingContainer {
func decodeHeterogeneousItem<F: HeterogeneousClassFamilyEx>(family: F.Type, forKey key: K) throws -> F.BaseType {
let container = try nestedContainer(keyedBy: Discriminator.self, forKey: key)
let family = try container.decode(F.self, forKey: F.discriminator)
let type = family.getType() // SubclassA.self, SubclassB.self concrete type
let item = try decode(type, forKey: key) // container still won't decode SubclassA or SubclassB but SuperClass
return item
}
}
let decodedRootEx = try decoder.decode(Page<HeterogeneousContainerEntityEx>.self, from: json.data(using: .utf8)!)
let isAEx = decodedRootEx.results[0].nestedObject is SubclassA // false
let isBEx = decodedRootEx.results[1].nestedObject is SubclassB // false
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment