Swift 4 Codable Enum with Associated Values 2
In the previous post, I gave an example of Coding/Decoding an enum
with
associated values using a helper struct
that mirrored the actual contents of the
JSON being parsed.
Here’s an example of doing the same thing, but directly impementing
init(from decoder:)
and encode(to encoder:)
, instead of using the helper struct.
Seems cleaner, right? The only snag is that when I’ve tried the two approaches with
my real-world data, I’ve found that this direct approach is noticeably slower.
When decoding an array of 1000 enums
, the helper struct version takes about 0.630s
and the direct decoding version takes about 0.733s. The effect seems to scale fairly
linearly (10000 takes 6.3s/7.3s etc.). Where’s that extra 15% of the time going?
I don’t know.
You can download a playground here.
import Foundation
enum Shape: Codable {
case rectangle(Double, Double)
case circle(Double)
enum CodingKeys: String, CodingKey {
case length
case width
case radius
}
// These are the ways the struct could be well-formed JSON, but incoherent
enum ShapeDecodingError: Error {
case missingLength
case missingWidth
case squaringTheCircle
case shapeless
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let length: Double? = try? container.decode(Double.self, forKey: .length)
let width: Double? = try? container.decode(Double.self, forKey: .width)
let radius: Double? = try? container.decode(Double.self, forKey: .radius)
switch (length, width, radius) {
case (nil, nil, nil):
throw ShapeDecodingError.shapeless
case (nil, _, nil):
throw ShapeDecodingError.missingLength
case (_, nil, nil):
throw ShapeDecodingError.missingWidth
case (.some(let l), .some(let w), nil):
self = .rectangle(l, w)
case (nil, nil, .some(let r)):
self = .circle(r)
default:
throw ShapeDecodingError.squaringTheCircle
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .rectangle(let length, let width):
try container.encode(length, forKey: .length)
try container.encode(width, forKey: .width)
case .circle(let radius):
try container.encode(radius, forKey: .radius)
}
}
}
let shapes = [Shape.rectangle(1.0, 2.0), Shape.circle(3.0)]
let encoder = JSONEncoder()
let jsonString = try String(data: encoder.encode(shapes), encoding:.utf8)
let decoder = JSONDecoder()
let json = """
[
{
"radius": 4.0
},
{
"length": 5.0,
"width": 6.0
}
]
""".data(using: .utf8)!
let shapes2 = try! decoder.decode([Shape].self, from: json)
shapes2[0]
shapes2[1]