Last active
April 27, 2020 16:26
-
-
Save saiday/c94f0f18f4bbb0b10775a6d3079a4f97 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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