LoginSignup
12
12

More than 5 years have passed since last update.

Codableで受け取るJSON文字列パターン集

Last updated at Posted at 2019-03-15

はじめに

最近JSONSerializationによるディクショナリ型パースからDecodableへ差し替える対応をごりごりしていたので、よく受け取る想定をしたパターンをまとめてみた。

クラッシュさせないために

まず、以下のJSON文字列を受け取るとする。

response.json
{
    "id": 123,
    "title": "タイトル"
}

Codableでこれを受け取ろうとすると以下のように書ける。

Response.swift
struct Response: Decodable {
    let id: Int
    let title: String
}

CodingKeysを使う場合はこう。

Response.swift
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になるとクラッシュする。

response.json
{
    "id": null,
    "title": null
}

オプショナルつけるとnull値が受け取れるようになる。

Response.swift
struct Response: Decodable {
    let id: Int?
    let title: String?
}

CodingKeysを使う場合は、decode先の型指定にオプショナルをつけることを忘れずに。

Response.swift
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を使って、イニシャライザを自前で定義しているときに判定漏れるとクラッシュする。

response.json
{
    "id": 123
}

今回の場合、titleキーが含まれているか確認後にデコードを行うようにする。

Response.swift
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など、単純な型以外を受け取りたい

配列

response.json
{
    "id": 123,
    "titles": ["タイトル0", "タイトル1", "タイトル2"]
}

受け取る型を変えるだけ。

Response.swift
struct Response: Decodable {
    let id: Int
    let titles: [String]
}

CodingKeysを使う場合もデコード時の型指定を変更するだけ。

Response.swift
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)
    }
}

入れ子

response.json
{
    "id": 123,
    "content": {
        "title": "タイトル"
    }
}

入れ子先の構造体分だけCodable準備。

Response.swift
struct Response: Decodable {
    let id: Int
    let content: Child
}

struct Child: Decodable {
    let title: String
}
Response.swift
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でも同じキーがくるからそれらの仕組みを使いまわしたいとか、ちょっと多すぎるから分けたいなどの場合。

response.json
{
    "id": 123,
    "title": "タイトル"
}
Response.swift
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
}
12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12