はじめに
最近JSONSerializationによるディクショナリ型パースからDecodableへ差し替える対応をごりごりしていたので、よく受け取る想定をしたパターンをまとめてみた。
クラッシュさせないために
まず、以下のJSON文字列を受け取るとする。
{
"id": 123,
"title": "タイトル"
}
Codableでこれを受け取ろうとすると以下のように書ける。
struct Response: Decodable {
let id: Int
let title: String
}
CodingKeysを使う場合はこう。
struct Response: Decodable {
let id: Int
let title: String
private enum CodingKeys: String, CodingKey {
case id
case title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
}
}
値null
上記のBodyクラスの書き方だとそれぞれ値がnullになるとクラッシュする。
{
"id": null,
"title": null
}
オプショナルつけるとnull値が受け取れるようになる。
struct Response: Decodable {
let id: Int?
let title: String?
}
CodingKeysを使う場合は、decode先の型指定にオプショナルをつけることを忘れずに。
struct Response: Decodable {
let id: Int?
let title: String?
private enum CodingKeys: String, CodingKey {
case id
case title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int?.self, forKey: .id)
title = try container.decode(String?.self, forKey: .title)
}
}
JSONのキー自体が存在しない場合
CodingKeysを使って、イニシャライザを自前で定義しているときに判定漏れるとクラッシュする。
{
"id": 123
}
今回の場合、titleキーが含まれているか確認後にデコードを行うようにする。
struct Response: Decodable {
let id: Int?
let title: String?
private enum CodingKeys: String, CodingKey {
case id
case title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int?.self, forKey: .id)
title = container.contains(.title) ? try container.decode(String?.self, forKey: .title) : nil
}
}
StringやIntなど、単純な型以外を受け取りたい
配列
{
"id": 123,
"titles": ["タイトル0", "タイトル1", "タイトル2"]
}
受け取る型を変えるだけ。
struct Response: Decodable {
let id: Int
let titles: [String]
}
CodingKeysを使う場合もデコード時の型指定を変更するだけ。
struct Response: Decodable {
let id: Int
let titles: [String]
private enum CodingKeys: String, CodingKey {
case id
case titles
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
titles = try container.decode([String].self, forKey: .titles)
}
}
入れ子
{
"id": 123,
"content": {
"title": "タイトル"
}
}
入れ子先の構造体分だけCodable準備。
struct Response: Decodable {
let id: Int
let content: Child
}
struct Child: Decodable {
let title: String
}
struct Response: Decodable {
let id: Int
let content: Child
private enum CodingKeys: String, CodingKey {
case id
case content
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
content = try container.decode(Child.self, forKey: .content)
}
}
struct Child: Decodable {
let title: String
}
なお、配列に構造体が入れ子になっている場合は上記Response.swiftのResponse構造体にて、Child型指定している箇所を[Child]型に差し替えれば動作する。
同階層の一部のキーだけ別の構造体に分けたい
別APIでも同じキーがくるからそれらの仕組みを使いまわしたいとか、ちょっと多すぎるから分けたいなどの場合。
{
"id": 123,
"title": "タイトル"
}
struct Response: Decodable {
let id: Int
let title: Title
private enum CodingKeys: String, CodingKey {
case id
}
init(from decoder: Decoder) throws {
title = try Title(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
}
}
struct Title: Decodable {
let title: String
}