アプリ内で保存しているCodableに準拠したstructを途中からRawRepresentableに準拠させたところ、デコード時にエラーが発生しました。今回はその調査結果を共有します。
問題のコード
まず、以下のようなコードがありました。
// Ver 1 struct Item: Codable { var id: String var name: String }
数カ月後、以下のようにRawRepresentableに準拠させたところ、もともと保存してあったものが復元できなくなりました。
// Ver 2 struct Item: Codable { var id: String var name: String } extension Item: RawRepresentable { // 普通はこんなことしないと思いますがサンプルコードなのでご了承ください var rawValue: String { "\(id)-\(name)" } init?(rawValue: String) { let components = rawValue.split(separator: "-") guard components.count == 2, let id = components.first, let name = components.last else { return nil } self.id = String(id) self.name = String(name) } }
何が起こったのか?
保存されていたデータがなぜ復元できなくなったのかを以下のコードで調べてみました。
let data = try! JSONEncoder().encode(Item(id: "id", name: "name")) print(String(data: data, encoding: .utf8))
Optional("{\"id\":\"id\",\"name\":\"name\"}")
想像通りのJSON形式です。
Ver 2はどうでしょうか?
Optional("\"id-name\"")
となりました。 rawValueがそのまま保存されるようです。
まとめ
このような動作の違いのため復元できなくなったようです。
RawRepresentableに準拠させると、エンコード時にrawValueが使われるため、従来のCodableのエンコード形式とは異なってしまいます。
今回紹介した例はシンプルなものですが、実際に私が遭遇したものはもっと複雑なstructの中の一部の値でこれが発生したため、原因がすぐにはわかりませんでした。
保存されているCodableに適合している値をRawRepresentableに適合させるときは注意が必要です。
もし必要であれば、自前で init(from: any Decoder) と encode(to encoder: any Encoder) を対応したほうがよさそうです。
私は今回そもそも、RawRepresentableへの適合が不要だと気がついたのでその必要はありませんでした。