Codableの使い方

2021年03月09日

JSONの戻り値を簡単にModelに適合させる事が出来ます。

少し前はObjectMapperなどのライブラリを使用する事で対応することが多かったですが、Swift3から Codable というプロトコルが実装され公式でサポートされました。

基本的な使い方

JSONのルートが1つの場合

// Codableを使ってJSONの値を適合させたいModel
struct Article: Codable {
    var id: Int
    var title: String
    var description: String
}
{
    "id": 1,
    "title": "記事のタイトル",
    "description": "記事の内容"
}
// APIやローカルのファイルなどからjsonをData型で読み込む
// 以下はローカルの sample.json をData型として読み込みArticleに変換している

guard let path = Bundle.main.path(forResource: "sample", ofType: "json") else { return true }
guard let data: Data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return true }

guard let article = try? JSONDecoder().decode(Article.self, from: data) else { return true }
// エラーが起きた場合にエラー内容を確認したい場合は以下のように
var article: Article!
do {
    article = try JSONDecoder().decode(Article.self, from: data)
} catch {
    print(error)
}

JSONのルートが配列の場合

[
    {
        "id": 1,
        "title": "記事のタイトル1",
        "description": "記事の内容1"
    },
    {
        "id": 2,
        "title": "記事のタイトル2",
        "description": "記事の内容2"
    }
]
guard let articles = try? JSONDecoder().decode([Article].self, from: data) else { return true }

JSONのルートがオブジェクト(ただし中身が配列の場合)

{
    "articles": [
        {
            "id": 1,
            "title": "記事のタイトル1",
            "description": "記事の内容1"
        },
        {
            "id": 2,
            "title": "記事のタイトル2",
            "description": "記事の内容2"
        }
    ]
}

struct Article: Codable {
    var id: Int
    var title: String
    var description: String
} 

struct ApiResponse: Codable {
    var articles: [Article]
}
guard let articles = try? JSONDecoder().decode(ApiResponse.self, from: data).articles else { return true }

レスポンスとkeyの値が違う場合

スネークケース、キャメルケースの差の場合

[
    {
        "id": 1,
        "title": "記事のタイトル1",
        "description": "記事の内容1",
        "release_date": "2020-01-01"
    },
    {
        "id": 2,
        "title": "記事のタイトル2",
        "description": "記事の内容2",
        "release_date": "2020-02-02"
    }
]
// Article.swift
struct Article: Codable {
    var id: Int
    var title: String
    var description: String
    var releaseDate: String
}
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
guard let articles = try? jsonDecoder.decode([Article].self, from: data) else { return true }
// => [CodableSample.Article(id: 1, title: "記事のタイトル1", description: "記事の内容1", releaseDate: "2020-01-01"), CodableSample.Article(id: 2, title: "記事のタイトル2", description: "記事の内容2", releaseDate: "2020-02-02")]

keyが全く違う場合

[
    {
        "article_id": 1,
        "article_title": "記事のタイトル1",
        "description": "記事の内容1",
        "release_date": "2020-01-01"
    },
    {
        "article_id": 2,
        "article_title": "記事のタイトル2",
        "description": "記事の内容2",
        "release_date": "2020-02-02"
    }
]
struct Article: Codable {
    var id: Int
    var title: String
    var description: String
    var releaseDate: String
    
    private enum CodingKeys: String, CodingKey {
        case id = "article_id"
        case title = "article_title"
        case description
        case releaseDate = "release_date"
    }
}
guard let articles = try? JSONDecoder().decode([Article].self, from: data) else { return true }
// => [CodableSample.Article(id: 1, title: "記事のタイトル1", description: "記事の内容1", releaseDate: "2020-01-01"), CodableSample.Article(id: 2, title: "記事のタイトル2", description: "記事の内容2", releaseDate: "2020-02-02")]

enumへの対応

[
    {
        "article_id": 1,
        "article_title": "記事のタイトル1",
        "description": "記事の内容1",
        "release_date": "2020-01-01",
        "type": "web"
    },
    {
        "article_id": 2,
        "article_title": "記事のタイトル2",
        "description": "記事の内容2",
        "release_date": "2020-02-02",
        "type": "book"
    },
    {
        "article_id": 3,
        "article_title": "記事のタイトル3",
        "description": "記事の内容3",
        "release_date": "2020-03-03",
        "type": "e-book"
    }
]
struct Article: Codable {
    var id: Int
    var title: String
    var description: String
    var releaseDate: String
    var type: ArticleType
    
    private enum CodingKeys: String, CodingKey {
        case id = "article_id"
        case title = "article_title"
        case description
        case releaseDate = "release_date"
        case type
    }
} 

enum ArticleType: String, Codable {
    case web
    case book
    case ebook = "e-book"
}
guard let articles = try? JSONDecoder().decode([Article].self, from: data) else { return true }
// => [CodableSample.Article(id: 1, title: "記事のタイトル1", description: "記事の内容1", releaseDate: "2020-01-01", type: CodableSample.ArticleType.web), CodableSample.Article(id: 2, title: "記事のタイトル2", description: "記事の内容2", releaseDate: "2020-02-02", type: CodableSample.ArticleType.book), CodableSample.Article(id: 3, title: "記事のタイトル3", description: "記事の内容3", releaseDate: "2020-03-03", type: CodableSample.ArticleType.ebook)]

入れ子の場合

[
    {
        "id": 1,
        "title": "記事のタイトル1",
        "description": "記事の内容1",
        "tags": [
            {
                "tag_id": 1,
                "name": "小説"
            }
        ]
    },
    {
        "id": 2,
        "title": "記事のタイトル2",
        "description": "記事の内容2",
        "tags": [
            {
                "tag_id": 1,
                "name": "小説"
            },
            {
                "tag_id": 2,
                "name": "人気"
            }
        ]
    }
]
struct Article: Codable {
    var id: Int
    var title: String
    var description: String
    var tags: [Tag]
} 

struct Tag: Codable {
    var id: Int
    var name: String
    
    private enum CodingKeys: String, CodingKey {
        case id = "tag_id"
        case name
    }
}
guard let articles = try? JSONDecoder().decode([Article].self, from: data) else { return true }
// => [CodableSample.Article(id: 1, title: "記事のタイトル1", description: "記事の内容1", tags: [CodableSample.Tag(id: 1, name: "小説")]), CodableSample.Article(id: 2, title: "記事のタイトル2", description: "記事の内容2", tags: [CodableSample.Tag(id: 1, name: "小説"), CodableSample.Tag(id: 2, name: "人気")])]

入れ子の中の必要な値のみを取得

[
    {
        "id": 1,
        "title": "記事のタイトル1",
        "description": "記事の内容1",
        "tag": {
            "tag_id": 1,
            "tag_name": "小説"
        }
    },
    {
        "id": 2,
        "title": "記事のタイトル2",
        "description": "記事の内容2",
        "tag": {
            "tag_id": 2,
            "tag_name": "人気"
        }
    }
]
struct Article: Codable {
    var id: Int
    var title: String
    var description: String
    var tagName: String

    private enum RootKeys: String, CodingKey {
        case id
        case title
        case description
        case tag
    }

    private enum CodingKeys: String, CodingKey {
        case tagName = "tag_name"
    }

    init(from decoder: Decoder) throws {
        let root = try decoder.container(keyedBy: RootKeys.self)

        id = try root.decode(Int.self, forKey: .id)
        title = try root.decode(String.self, forKey: .title)
        description = try root.decode(String.self, forKey: .description)

        let tag = try root.nestedContainer(keyedBy: CodingKeys.self, forKey: .tag)
        tagName = try tag.decode(String.self, forKey: .tagName)
    }
} 
guard let articles = try? JSONDecoder().decode([Article].self, from: data) else { return true }
// => [CodableSample.Article(id: 1, title: "記事のタイトル1", description: "記事の内容1", tagName: "小説"), CodableSample.Article(id: 2, title: "記事のタイトル2", description: "記事の内容2", tagName: "人気")]

おすすめ