【Swift】Codableについて備忘録
Codableについて今まで触れてなかったので調べてみました。
忘れないうちにまとめてみます。
Codableって何?
API通信等で取得したJSONやプロパティリストを任意のデータ型に変換するプロトコル
→ データをアプリを実装しやすいデータ型に変換することで処理が楽になる
(例)ユーザ情報のJSONを「User型」に変換する
json = {
"name": "太郎"
"age": "20"
}
let name = json["name"]
struct User: Codable {
name: String
age: String
}
let name = User.name
Encodable, Decodable との関係
初めてコードを見た時、EncodableとかDecodableとか複数あって混乱しましたが
Codable = Encodable + Decodable
ってことみたいです。
自動Codable
・常にCodableな型
String, Int, Double, Data, Date, URL
・条件付きでCodableな型
Array, Dictionary, Optional → 中身がCodableの場合
struct → Codableなプロパティのみから構成されている場合
↓↓こんな感じで書くとCodableとして使える↓↓
struct Friend: Codable { // -> プロパティが全てCodableなのでCodable
name: String // -> Codable
age: Int // -> Codable
}
struct User: Codable {
name: String
age: Int
friends: [Friend] // -> 中身がCodableなのでCodable
startDate: Date
role: String
}
CodingKeysの利用
上記のように記述すればCodableとして使えるけど、EncodeとDecodeでキー名が異なる時に一対一対応させる必要がある。その時に使うのが「CodingKeys」。
↓↓こんな感じ使う↓↓
struct User: Codable {
name: String
age: Int
friends: [Friend]
startDate: Date
role: String
enum CodingKeys: String, CodingKey {
case name
case age
case friends
case startDate = "start_date" // ← これ
case role
}
}
【実装する時の決まり事】
・enumの名前は「CodingKeys」にする
・case名をプロパティ名、rawValueをエンコード結果のフィールド名として定義する
・case自体を省略するとエンコード・デコードされない。この時、Decodableにするにはdefault valueが必要。
例で示した@「SnakeCase ↔︎ CamelCase」の場合は
Swift4.1以降では、DecoderのkeyDecodingStrategyを使うと省略できるみたいです。
参考ページはこちら
手動Codable
定義したデータ構造がエンコードのフォーマットと合わない場合は、EncodeとDecodeの処理を自分で実装する必要がある。
(例)
ネストしている場合
struct User: Codable {
name: String
age: Int
friends: [Friend]
startDate: Date
role: String // ← これ
enum CodingKeys: String, CodingKey {
case name
case age
case friends
case startDate = "start_date"
case additionalInfo // ← これ
}
enum AdditionalInfoKeys: String, CodingKey {
case role
}
}
● Decode
extension User: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(Double.self, forKey: .name)
age = try values.decode(Double.self, forKey: .age)
friends = try values.decode(Double.self, forKey: .friends)
startDate = try values.decode(Double.self, forKey: .startDate)
let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
role = try additionalInfo.decode(Double.self, forKey: .role)
}
}
● Encode
extension User: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
try container.encode(friends, forKey: .friends)
try container.encode(startDate, forKey: .startDate)
var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
try additionalInfo.encode(role, forKey: .role)
}
}
Encoder, Decoder
● Decoder
let decoder = JSONDecoder()
let data = decoder.decode(**変換する型**.self, json: json)
● Encoder
let encoder = JSONEncoder()
// JSONにエンコードする型のデータ
let user = User(name: "太郎", age: 20)
let data = encoder.encode(user)
継承クラスのエンコード
- 子のクラスのみ「Codable」
→ 親クラスのプロパティはエンコードされない - 親のクラスのみ「Codable」
→ 子クラスのプロパティはエンコードされない
↓↓継承クラスのエンコードの場合、手動で実装する必要がある。↓↓
class Animal {
var name = "Animal"
}
class Cat: Animal, Encodable {
var catName = "Cat"
enum CodingKeys: String, CodingKey {
case name
case catName
}
}
extension Cat: Animal, Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(catName, forKey: .catName)
try container.encode(name, forKey: .name)
}
}
DecodingContainer, EncodingContainer
自分で実装する時に使う。
● Decode
名称 | 型 |
---|---|
KeyedContainer | 辞書 |
UnkeyedDecodingContainer | 配列 |
SingleValueDecodingContainer | 単一値 |
extension User: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(Double.self, forKey: .name)
age = try values.decode(Double.self, forKey: .age)
}
}
● Encode
名称 | 型 |
---|---|
KeyedEncodingContainer | 辞書 |
UnkeyedEncodingContainer | 配列 |
SingleValueEncodingContainer | 単一値 |
extension User: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
}
}
ネストさせる場合は、nestedContainerを使う。
// 辞書
container.nestedContainer
// 配列
container.nestedUnKeyedContainer
まとめ
これまでObjectMapperなどライブラリを使っていましたが、Codableで十分だなと感じました。
間違い等ございましたら、ご指摘お願いします。
Discussion