Swiftで保存している値を途中からRawRepresentableに適合させたら復元できなくなった

アプリ内で保存している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への適合が不要だと気がついたのでその必要はありませんでした。