【Swift】Codableについて備忘録

#記事を移動しました。
https://zenn.dev/dd_sho/articles/45de4efe7dc296
今後は上記の記事で更新します。（2021/10/03）

Codableについて今まで触れてなかったので調べてみました。
忘れないうちにまとめてみます。

## Codableって何？
API通信等で取得したJSONやプロパティリストを任意のデータ型に変換するプロトコル
→　データをアプリを実装しやすいデータ型に変換することで処理が楽になる

[Apple Reference はこちら](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)

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

```
json = {
  "name": "太郎"
  "age": "20"
}

let name = json["name"]
```
```swift: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として使える↓↓

```swift: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」。

↓↓こんな感じ使う↓↓

```swift: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**を使うと省略できるみたいです。  
[参考ページはこちら](https://www.hackingwithswift.com/articles/52/swift-4-1-improves-codable-with-keydecodingstrategy)

## 手動Codable
定義したデータ構造がエンコードのフォーマットと合わない場合は、EncodeとDecodeの処理を自分で実装する必要がある。

（例）
ネストしている場合

```swift: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

```swift: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

```swift: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

```swift
let decoder = JSONDecoder()
let data = decoder.decode(**変換する型**.self, json: json)
```
[Apple Reference はこちら](https://developer.apple.com/documentation/foundation/jsondecoder)  

● Encoder

```swift
let encoder = JSONEncoder()
// JSONにエンコードする型のデータ
let user = User(name: "太郎", age: 20)
let data = encoder.encode(user)
```
[Apple Reference はこちら](https://developer.apple.com/documentation/foundation/jsonencoder)  

## 継承クラスのエンコード
1. 子のクラスのみ「Codable」  
　→　親クラスのプロパティはエンコードされない
2. 親のクラスのみ「Codable」  
　→　子クラスのプロパティはエンコードされない  

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

```swift: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 | 単一値 |

```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)
  }
}
```

● Encode 

| 名称 | 型 |
|:------------------:|:------------------:|
| KeyedEncodingContainer | 辞書 |
| UnkeyedEncodingContainer | 配列 |
| SingleValueEncodingContainer | 単一値 |

```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)
  }
}
```

ネストさせる場合は、nestedContainerを使う。

```swift

// 辞書
container.nestedContainer

// 配列
container.nestedUnKeyedContainer
```

## まとめ
これまでObjectMapperなどライブラリを使っていましたが、Codableで十分だなと感じました。
間違い等ございましたら、ご指摘お願いします。