LoginSignup
18
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-10

Swift4 から Codableが導入されましたね。
Codable が解決してくれた問題の一つに JSON を通してデータのやり取りをするようなアプリにおいて定義してる構造体と 対応するJSON のマッピングが楽になりました。

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

それ以前、Swift3まではObjectMapperというライブラリを好んで使っていました。

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

Enumの配列をデコードしてみる

下記のようなCodable に準拠した structenum があったとして、それぞれの関係も記します。

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

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

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

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 になったり


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 の文字列の方です。


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

こうですね

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


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

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

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


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 して見ましょう。


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 の時には変換できなかったものは無視する(ここら辺
Codable(正確にはDecodable)の場合は例外を発する(ここだ!って場所見つけられなかった)

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

どういう時に危ない?

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

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

終わりに

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

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

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

として書いてみました。

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

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

おしまい \(^o^)/

18
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
18
4