Raw Valueを Int
や String
にしたenum型は、比較的簡単に Codable
に準拠できます。しかし以下のようなAssociated Valueを持たせたcaseを含むenum型は、デコード・エンコード処理を独自に実装する必要があります。
enum Variant: Codable {
case bool(Bool)
case int(Int)
case string(String)
case point(CGPoint)
case rect(CGRect)
}
まず各case名と同じ Codingkeys
を定義し、encode()
を実装します。
extension Variant: Codable {
enum CodingKeys: String, CodingKey {
case bool
case int
case string
case point
case rect
}
// ...デコードの方法は後述...
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .bool(let value):
try container.encode(value, forKey: .bool)
case .int(let value):
try container.encode(value, forKey: .int)
case .string(let value):
try container.encode(value, forKey: .string)
case .point(let point):
try container.encode(point, forKey: .point)
case .rect(let rect):
try container.encode(rect, forKey: .rect)
}
}
}
この実装でJSONエンコードを行うと、次のような結果になります。
let variants: [Variant] = [
.bool(true),
.int(123),
.string("hoge"),
.point(CGPoint(x: 100, y: 200)),
.rect(CGRect(x: 10, y: 20, width: 30, height: 40))
]
let json = try! JSONEncoder().encode(variants)
print(String(data: json, encoding: .utf8)!)
[
{"bool" : true},
{"int" : 123},
{"string" : "hoge"},
{"point" : [100, 200]}
{"rect" : [[10, 20], [30, 40]]}
]
オブジェクトの中身を見て型推定することになるため、REST APIのレスポンス型としては妥当ではないかもしれません。
UserDefaults
やローカルストレージへ保存する目的では問題ないでしょう。
このJSONをデコードする実装は次のようになります。
extension Variant: Codable {
enum CodingKeys: String, CodingKey { ... }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(Bool.self, forKey: .bool) {
self = .bool(value)
} else if let value = try container.decodeIfPresent(Int.self, forKey: .int) {
self = .int(value)
} else if let value = try container.decodeIfPresent(String.self, forKey: .string) {
self = .string(value)
} else if let point = try container.decodeIfPresent(CGPoint.self, forKey: .point) {
self = .point(point)
} else if let rect = try container.decodeIfPresent(CGRect.self, forKey: .rect) {
self = .rect(rect)
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Unknown case"
))
}
}
func encode(to encoder: Encoder) throws { ... }
}
構造が一致しない場合は自分でハンドリングが必要です。
上記の例では DecodingError.dataCorrupted
を投げるようにしています。
1つのcaseにAssociated Valueが複数ある場合は実装が難しくなります。
複数のAssociated Valueは1つの構造体(struct)にまとめ、これをCodable
に準拠させるほうが簡単でしょう。
参考: https://medium.com/@hllmandel/codable-enum-with-associated-values-swift-4-e7d75d6f4370