LoginSignup
11
4

More than 5 years have passed since last update.

Associated Valueを含むenum型をCodableに準拠する

Last updated at Posted at 2019-02-26

Raw Valueを IntString にしたenum型は、比較的簡単に Codable に準拠できます。しかし以下のようなAssociated Valueを持たせたcaseを含むenum型は、デコード・エンコード処理を独自に実装する必要があります。

Variant.swift
enum Variant: Codable {
    case bool(Bool)
    case int(Int)
    case string(String)
    case point(CGPoint)
    case rect(CGRect)
}

まず各case名と同じ Codingkeys を定義し、encode() を実装します。

Variant.swift
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をデコードする実装は次のようになります。

Variant.swift
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

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4