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: "人気")]