URLSessionとCodableでのJSON通信を実装してつまずいた話

トラブルシューティング

SwiftUI Chartsでグラフ表示ができるようになった流れで、次は外部APIから取得したデータをグラフに表示したいと考え、URLSessionとCodableを使ったJSON通信に挑戦しました。

1. モデルの定義さえ合っていれば必ずデコードできると思っていた

最初、APIのレスポンス構造に合わせてCodable準拠の構造体を定義しさえすれば、必ずデコードに成功するものだと思い込んでいました。

struct Record: Codable {
    let id: Int
    let title: String
}

実際にAPIを叩いてみると、想定していなかったフィールドの型違いや、稀に返ってくるnull値によってデコードがそのまま失敗しました。モデルの定義は「正常なレスポンス」だけを想定していたため、実際のレスポンスのばらつきに対応できていなかったと気づきました。

2. デコード失敗時のエラー内容を確認していなかった

デコードが失敗するたびに、原因を推測だけで探そうとして時間がかかっていました。

実際に出ていたエラー:
keyNotFound(CodingKeys.title, ...)

do-catchでエラーを握りつぶさずにきちんと出力するようにしたところ、どのキーが見つからないか、どの型が期待と違うかが一目で分かるようになりました。エラーの中身を確認せずに当てずっぽうで直そうとしていたのが、時間を無駄にしていた原因でした。

3. すべてのプロパティを必須項目にしてしまっていた

APIから返ってくる値の中に、場合によっては存在しないフィールドがあったのですが、最初はすべてのプロパティを非オプショナルで定義してしまっていました。

struct Record: Codable {
    let id: Int
    let note: String?
}

存在しない可能性があるフィールドはOptionalにする必要があり、そうしないと該当フィールドがないレスポンスが返ってきた瞬間にデコード全体が失敗してしまいます。「APIドキュメント通りに来るはず」という思い込みを捨て、欠けている可能性を前提に設計する必要があると学びました。

4. 通信エラーとデコードエラーを区別せずに同じ表示にしていた

通信自体が失敗した場合と、通信は成功したがデコードに失敗した場合を、最初は同じ「読み込みに失敗しました」という表示でひとまとめにしていました。

ユーザーからすると、ネットワーク環境が悪いのか、アプリ側の不具合なのかが分からず不安になる表示でした。URLErrorとDecodingErrorを明確に分けてキャッチし、通信エラーの場合は「通信環境をご確認ください」、デコードエラーの場合は「情報の取得に問題が発生しました」という具合に、原因に応じたメッセージを出し分けるように直しました。

まとめ

  • Codableのモデル定義は正常系だけでなく、型のばらつきや欠損フィールドの可能性も考慮して設計する
  • デコード失敗時はエラーを握りつぶさず、内容を確認してから対処する
  • 存在しない可能性があるフィールドはOptionalにし、欠けている前提で設計する
  • 通信エラーとデコードエラーは原因が異なるため、ユーザーへの表示も分けて出す

JSON通信は仕組み自体はシンプルですが、現実のAPIレスポンスのばらつきにどう備えるかで実装の質が大きく変わると実感しました。

次は、Core Locationでのバックグラウンド位置情報取得で分かったことをまとめます。


本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。

コメント

タイトルとURLをコピーしました