CodableでEnumの配列をデコードするときに注意すること

`Swift4` から [Codable](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)が導入されましたね。
`Codable` が解決してくれた問題の一つに `JSON` を通してデータのやり取りをするようなアプリにおいて定義してる構造体と 対応する`JSON` のマッピングが楽になりました。

僕が開発しているアプリでもサーバーとのデータのやり取りは `JSON` を使用していました。
`Swift4` が使えるようになってからは積極的に `Codable` を使っています。

それ以前、Swift3までは[ObjectMapper](https://github.com/Hearst-DD/ObjectMapper)というライブラリを好んで使っていました。

もともと`ObjectMapper` を使っていて、その考え方で`Codable` を使っていたら危険だな、と思った話をしていこうと思います。
今回そう思うきっかけになったのが `CodableでEnumの配列をデコードするとき` だったのでそれを例にして書いていこうと思います。

## Enumの配列をデコードしてみる
下記のような`Codable` に準拠した `struct` と`enum` があったとして、それぞれの関係も記します。


```swift
enum SocialAccountType: String, Codable {
    case twitter
    case facebook
}

struct User: Codable {
    let id: Int
    let accounts: [SocialAccountType]
}
```


`Decode` をすると下記のような結果になります

```swift
let response = """
{
    "id": 1,
    "accounts": ["facebook", "twitter"]
}
"""

let json = response.data(using: .utf8)!
let decoded = try！ JSONDecoder().decode(User.self, from: json)  
print(decoded) 
// User(id: 1, accounts: [SocialAccountType.facebook, SocialAccountType.twitter])
```

ちゃんと`Decode` できてますね。

次は`ObjectMapper`ではどうなるか見ていきましょう。
宣言が`var` になったり `Optional` になったり

```swift

enum SocialAccountType: String {
    case twitter
    case facebook
}

struct User: Mappable {
    var id: Int?
    var accounts: [SocialAccountType]?
    init?(map: Map) {
        id <- map["id"]
        accounts <- map["accounts"]
    }
    mutating func mapping(map: Map) { }
}
```

同じように `JSON` の文字列から `mapping` して `User` のインスタンスを作りたいと思います

```
let user = Mapper<User>().map(JSONString: response)!
print(user)
// User(id: Optional(1), accounts: Optional([SocialAccountType.facebook, SocialAccountType.twitter]))
```

`Optional` が付いてますがちゃんと `mapping` ができていると思います。


## 本題
さて、仮に上のロジックでアプリがリリースされている場合を想定しましょう。
そして、`SocialAccountType`に一つ要素を足したくなたっときの場合を想定します。
ここでは`qiita` という要素を`SocialAccountType` に足していきます。
しかし、`Swift` ファイルの方は編集しないです。編集するのは `JSON` の文字列の方です。

```swift

let response = """
{
    "id": 1,
    "accounts": ["facebook", "twitter", "qiita"] 
}
"""
```

こうですね

さて、この場合で先ほどの`Codable`, `ObjectMapper` 両パターンで実際に動かすとどうなるでしょう
順序を変えて `ObjectMapper` から

```swift

let user = Mapper<User>().map(JSONString: response)!
print(user)
// User(id: Optional(1), accounts: Optional([SocialAccountType.facebook, SocialAccountType.twitter]))
```

先ほどと実行結果がかわあず

さて次に `Codable` をやっていきます。

```swift

let json = response.data(using: .utf8)!
let decoded = try JSONDecoder().decode(User.self, from: json)
print("decoded value: \(decoded)")
```

`Playground` 上では何も出力されませんでした。
これは `try JSONDecoder().decode(User.self, from: json)` で例外が起きたためです。
`catch` して `print` して見ましょう。

```swift

let json = response.data(using: .utf8)!
do {
    let decoded = try JSONDecoder().decode(User.self, from: json)
    print("decoded value: \(decoded)")
} catch {
    print("error: \(error.localizedDescription)")
   // error: The data couldn’t be read because it isn’t in the correct format.
}
```

フォーマット違うから読み込めなかったよ。って怒られましたね。

`JSON` から `Enum` の配列を　`Decode` するようなときには 
`ObjectMapper` の時には変換できなかったものは無視する([ここら辺](https://github.com/Hearst-DD/ObjectMapper/blob/c092a1d7696eba6eede957b0905b8b9fa91fd8d1/Sources/TransformOperators.swift#L593)
`Codable`(正確には`Decodable`)の場合は例外を発する(ここだ！って場所見つけられなかった)

という結果になりました。

## どういう時に危ない？
さて、挙動の違いがわかったところでどういうシチュエーションで危なそうかちょっと触れてまとめたいと思います。
危ないというよりは思い込みにより予期せぬことが起き得る可能性があります。

例えば`JSON` でサーバーとのやり取りをしているアプリで想定して、少し前まで `ObjectMapper` を使っていましたが、`Codable` に書き換えました。問題なく動作していて、なんだ`Codable`、 `ObjectMapper`と同じような使い方できるじゃねえか、いいやつだな。おい。と思ってました。 そんな時に`qiita`という要素を足すことになりました。`ObjectMapper` の感覚では存在しない`case`は無視されて、`accounts`にはそのアプリのバージョンで`.swift`に書かれている `case`の要素分の配列が作られるはずだから特に新しい要素が増えることに対して個別に対応する必要もないし、ただ表示するような用途しか使ってないから大丈夫大丈夫。と先入観がある可能性が大いにあり得ます。こんな状態で`Codable` を使った場合は先ほどの結果からわかるように例外を発するのでアプリが何かしら予期していなかった動きになる可能性があります。


## 終わりに
具体的に何を選択するのか、アプリのバージョニングによる差分はどうやって埋めるのがいいか、といったベストプラクティスは持ち合わせていないので、ここでは書きません。むしろどなたか教えてください。

他に `Codable` の癖みたいなのもまだ把握しきっていないので

> CodableでEnumの配列をデコードするときに注意すること

として書いてみました。

ここまでの比較で `ObjectMapper` を例にしてあげましたが、`ObjectMapper` じゃなくても `Codable` との差異がある可能性は大いにあるし、どっちがいいか悪いかといった記事では無いことをご理解いただけると幸いです。

`Codable` と 今まで使っていた `ORM` の機能の差はあるかもしれない。と先入観持たずに取り組みましょう。という記事でした。

おしまい \\(^o^)/