Edited at

【Swift】Codableについて備忘録

Codableについて今まで触れてなかったので調べてみました。

忘れないうちにまとめてみます。


Codableって何?

API通信等で取得したJSONやプロパティリストを任意のデータ型に変換するプロトコル

→ データをアプリを実装しやすいデータ型に変換することで処理が楽になる

Apple Reference はこちら

(例)ユーザ情報のJSONを「User型」に変換する

json = {

"name": "太郎"
"age": "20"
}

let name = json["name"]


User.swift


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として使える↓↓


User.swift


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」。

↓↓こんな感じ使う↓↓


User.swift

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の処理を自分で実装する必要がある。

(例)

ネストしている場合


User.swift

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


User.swift

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


User.swift

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)

Apple Reference はこちら

● Encoder

let encoder = JSONEncoder()

// JSONにエンコードする型のデータ
let user = User(name: "太郎", age: 20)
let data = encoder.encode(user)

Apple Reference はこちら


継承クラスのエンコード


  1. 子のクラスのみ「Codable」

     → 親クラスのプロパティはエンコードされない

  2. 親のクラスのみ「Codable」

     → 子クラスのプロパティはエンコードされない


↓↓継承クラスのエンコードの場合、手動で実装する必要がある。↓↓


Cat.swift

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で十分だなと感じました。

間違い等ございましたら、ご指摘お願いします。