Codableは最初、魔法のように見えます。structに: Codableの5文字を付けるだけで、JSON変換が無料で手に入るからです。しかし実際のAPIにつなぐと魔法は解けます。サーバーはsnake_case、SwiftはcamelCase、日付形式はAPIごとに異なり、一覧の1件が壊れているだけで全体のデコードに失敗し、画面が空になります。
中級シリーズ第6回はCodableの応用です。自動合成が何を生成するのかから、実務で必ず遭遇する4つの状況(キー名、日付、ネスト、部分的な失敗)への標準的な対処まで整理します。
魔法の正体 — コンパイラが代わりに書くコード
CodableはEncodable & Decodableのtypealiasで、「エンコーダーへの書き込み方」と「デコーダーからの生成方法」を要求するプロトコルです。魔法に見えるのはコンパイラの自動合成(synthesis)によるものです。すべてのstored propertyがCodableなら、コンパイラが2つを代わりに実装します。
1つ目はCodingKeysというenumです。プロパティ名をそのままcaseにしたキー一覧です。2つ目は、そのキーで各プロパティを読み書きするinit(from:)とencode(to:)の実装です。つまりCodableのカスタマイズとは、2つの生成物のどちらを手書きで置き換えるかという問題です。キー名だけが違うならCodingKeys、構造自体が違うならinit(from:)まで実装します。この見取り図があれば、以降は「どこまで手書きするか」の問題として整理できます。
もう1つ重要な点があります。CodableはJSON専用ではありません。エンコーダーとデコーダーを交換できる構造なので、PropertyListEncoderでplistを、サードパーティ製ライブラリでXMLやYAMLを扱えます。型は「自分をどう表現するか」だけを知り、形式はエンコーダーが決めます。これは関心の分離です。
状況1. キー名が異なる場合 — CodingKeysとキー戦略
サーバーがuser_nameを返す一方、プロパティはuserNameにしたい場合、対処は2段階です。
全体の規則が一貫しているなら、デコーダーの設定1行で済みます。decoder.keyDecodingStrategy = .convertFromSnakeCase。すべてのキーがsnake_caseからcamelCaseへ自動変換されます。API全体が慣例に従っているなら、これが正解です。
規則が不揃い、または名前そのものを変更したい場合は、CodingKeysを直接定義します。
struct User: Codable {
let userName: String
let signupDate: Date
enum CodingKeys: String, CodingKey {
case userName = "user_nm" // サーバーのレガシーキー
case signupDate = "created"
}
}
副作用として覚えておきたい点が1つあります。CodingKeysからcaseを外すと、そのプロパティはエンコード・デコードから除外されます。キャッシュフラグなどのローカル専用状態をレスポンスモデルに持たせるときに便利ですが、除外したプロパティにはデフォルト値が必要です。
状況2. 日付 — 形式の落とし穴
DateはCodableの実務で最も頻繁に問題になる落とし穴です。JSONには日付の標準がなく、サーバーごとにUnixタイムスタンプ(1720000000)、ISO 8601(“2026-07-15T09:30:00Z”)、カスタム形式(“2026-07-15 09:30”)がばらばらだからです。
対処は、デコーダーのdateDecodingStrategyをサーバーの形式に合わせることです。.secondsSince1970、.iso8601、カスタム形式なら.formatted(formatter)を使います。カスタムDateFormatterには2つの罠があります。localeをen_US_POSIXに固定しないと、ユーザーの12/24時間設定によってパースに失敗します。また、サーバーがミリ秒付きISO 8601を返すと標準の.iso8601は失敗します。ISO8601DateFormatterでミリ秒オプションを有効にすれば解決できます。「特定のユーザーだけ日付パースに失敗する」という謎のバグの大半はこの2つが原因です。
1つのAPI内でフィールドごとに形式が異なる最悪のケースでは、そのフィールドだけStringで受けてcomputed propertyで変換するか、.custom戦略で分岐するのが現実的です。
状況3. ネストと構造の不一致 — サーバーの形と自分の形
サーバーのJSONが深くラップされて返ってくるケースです。たとえば{"data": {"user": {...}}}のような形です。最も単純な対処は、封筒型をそのまま作ることです。struct Envelope: Codable { let data: DataBox }のようにサーバー構造をミラーリングし、呼び出し側でenvelope.data.userとして取り出します。明示的でデバッグしやすいため、実務の基本としてはこちらが適しています。
サーバーと異なる形のモデル、たとえばフラットなUserにしたいなら、init(from:)を手書きしてnestedContainerで階層を掘り下げます。コードは増えますが、モデルはドメイン中心で明快になります。判断基準は、そのモデルがどれだけ広く使われるかです。アプリ全体で使う中核モデルなら手書きのinit(from:)に価値がありますが、1画面だけのレスポンスなら封筒のミラーリングが経済的です。
状況4. 部分的な失敗 — 1件のために全体が落ちる問題
実務で最もつらい状況です。商品100件の一覧で1件の必須フィールドがnullだと、配列全体のデコードがthrowされ、画面が空になります。エラー処理編で見たように、throwされたエラーは伝播します。
第一の防御はOptionalです。サーバーが欠落させる可能性のあるフィールドは、正直にlet thumbnail: URL?として宣言します。「存在しない可能性」を型に刻むOptionalの原則が、モデル設計にもそのまま適用されます。
構造的な防御は、失敗を許容するラッパーです。要素のデコード失敗をnilとして扱うジェネリックラッパーを用意するパターンが、標準的に使われています。
struct FailableItem<T: Decodable>: Decodable {
let value: T?
init(from decoder: Decoder) throws {
value = try? T(from: decoder) // 失敗したらnil nil
}
}
let items = try decoder.decode([FailableItem<Product>].self, from: data)
.compactMap(\.value) // 成功したものだけ残す
try?とcompactMapという、これまでに扱った2つの道具の組み合わせです。「不正な1件は捨て、残りを生かす」という方針を1つの型で表現しています。ただし、このパターンは失敗を静かに握りつぶします。本番では失敗件数をログに記録し、サーバーデータの問題を隠さないようにしてください。
デバッグ — DecodingErrorは答えを知っている
最後に、デコードに失敗したときの調べ方です。try decoder.decode(...)をcatchするとDecodingErrorを捕捉できますが、このエラーは想像以上に親切です。4つのケース(keyNotFound、typeMismatch、valueNotFound、dataCorrupted)ごとに、どのキーが、どのcodingPathで、何を期待し、何が届いたのかが含まれています。
デコードに失敗したら、print(error)ではなくprint(error as? DecodingError)を調べる習慣をつけましょう。少なくとも開発中は、catchブロックでケースごとにcodingPathを出力します。デバッグ時間を大幅に短縮できます。「JSONをパースできません」の大半は、エラーの中にすでに答えがあります。
まとめ
- Codableの魔法はコンパイラ合成です。CodingKeys enumとinit(from:)/encode(to:)を代わりに書いてくれます。カスタマイズとは、そのうちどこまでを手書きで置き換えるかという問題です。
- キー名:一貫したsnake_caseならkeyDecodingStrategyを1行、不規則ならCodingKeysを手動定義します。caseを外すと、そのフィールドは変換から除外されます。
- 日付:サーバー形式に合うdateDecodingStrategyを明示し、en_US_POSIX localeとミリ秒付きISO 8601の罠に注意します。
- ネスト:基本は封筒をミラーリングし、中核モデルだけinit(from:) + nestedContainerでフラット化します。
- 部分的な失敗:Optionalを正直に宣言し、FailableItemパターン(try? + compactMap)を使います。握りつぶした失敗はログで可視化します。
- デバッグ:答えはDecodingErrorのcodingPathにあります。
次回は@Stateや@Publishedのようなアットマーク構文の原理、プロパティラッパーを扱います。プロパティ編で予告した「stored propertyを包む構文」を実際に作ります。

![[Swift中級 #6] Swift Codable徹底解説、4つの落とし穴への処方箋のカバー画像](/assets/images/posts/c82f52ea-3b48-40d5-8739-a94bd38731cf/swift-codable-deep-dive-1.jpg)