Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

最近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
}
m-yamada1992
現在、スマートフォンアプリの開発業務を中心に担当。 興味はいろいろ。
studyplus
学習管理アプリ「Studyplus」を開発・運営する会社です
https://info.studyplus.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away