I want to decode a dictionary with different values. So while the key will always be of type String
, the value will have the same superclass
(like Shape
) but might be composed out of different subclasses
(like Rectangle
, Circle
). I want to be able to later check which subclass
is attached, but so far I can only use the default decoding into [ AttachedObject: Shape ]
.
See the example:
enum AttachedObject: String, Codable {
case chair
case lamp
case desk
}
class Shape: Codable {
var name: String
init(name: String) {
self.name = name
}
}
class Rectangle: Shape {
var width: Double
var height: Double
init(name: String, width: Double, height: Double) {
self.width = width
self.height = height
super.init(name: name)
}
enum CodingKeys: String, CodingKey {
case width
case height
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.height, forKey: .height)
}
required public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.width = try values.decode(Double.self, forKey: .width)
self.height = try values.decode(Double.self, forKey: .height)
try super.init(from: decoder)
}
}
class Circle: Shape {
var radius: Double
init(name: String, radius: Double) {
self.radius = radius
super.init(name: name)
}
enum CodingKeys: String, CodingKey {
case radius
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.radius, forKey: .radius)
}
required public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.radius = try values.decode(Double.self, forKey: .radius)
try super.init(from: decoder)
}
}
class MyRoom: Codable {
public var attachedShapes: [ AttachedObject: Shape ]
enum CodingKeys: String, CodingKey {
case attachedShapes
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.attachedShapes, forKey: .attachedShapes)
}
required public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
fatalError("// How to handle the decoding part?")
}
}
I would go with something like this:
enum ShapeType: String, RawRepresentable, Codable {
// Required for RawRepresentable
static var defaultDecoderValue: ShapeType = .circle
case circle
case rectangle
}
struct Shape: Codable {
let name: String
let width: Double?
let height: Double?
let radius: Double?
let type: ShapeType
}
Then you don't need any custom keys. You can always refer to any of the Shape's in arrays, etc. You can look at ShapeType to see whether it's a rect or circle. You can make them var's instead of lets if you need to change them, and you can make is Class Shape instead of Struct Shape if you want a class instead.