SwiftでCodableの実装を爆速でする方法

## はじめに
- サーバとの通信処理ではResponseの値をmappingやparseして、Swiftで扱いやすいように変換する処理をよく書きます。JSONからSwiftの値に変換するときにはCodableを使うと楽に実装できます。そんなCodableの実装もAPIが増えるごとにたくさん実装、メンテする必要がでてきます。そんな実装を爆速でするための方法について、まとめました。

## JSONの型とSwiftで使う型の変換
```json
{ "name": "Tanaka", "age": "26", "gender": "0" }
```
- このようなJSONあるときに実装で扱いやすい型に変換するときに苦労した経験があります。(String <-> Intの変換など)
Codableで逐次変換処理を書くと可読性が落ちて、メンテし辛いコードが出来上がります。それを克服するためにDTOを使って、可読性の高いコードにしてみます。

## DTOを使ったCodableの処理
#### DTOとは
- `Data Transfer Object`を省略したものです。
- Layer間の値の受け渡しに使います。

- このJSONをSWiftの値に変換する処理を書いてみます。

```json
{ "name": "Tanaka", "age": "26", "gender": "0" }
```

- before
    - 普通に書くと`try` の処理と型変換の処理がたくさん書くことになります

```swift
import UIKit

let json = """
{ "name": "Tanaka", "age": "26", "gender": "0" }
""".data(using: .utf8)!

enum Gender: Int {
    case male = 0
    case female
    case unknown
}

struct Person {
    let name: String
    let age: Int
    let gender: Gender
    
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case gender
    }
}
enum DecodableError: Error {
    case ageError
    case genderError
}

extension Person: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        let ageValue = try container.decode(String.self, forKey: .age)
        guard let age = Int(ageValue) else {
            throw DecodableError.ageError
        }
        self.age = age
        
        let genderString = try container.decode(String.self, forKey: .gender)
        guard let genderValue = Int(genderString), let gender = Gender(rawValue: genderValue) else {
            throw DecodableError.genderError
        }
        self.gender = gender
    }
}

let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: json)
```

- after
    - `DTODecodable`を使って、tryの処理を書かなくていいようにします。DTOではJSONで宣言されている型の通りに変換することで値のparseのみ行います。Entity側ではDTOの値を元にEntityで必要な型に変換します。Entity側ではtryの処理を意識しないのでコードの可読性が上がりました。

```swift

import UIKit

protocol DTODecodable: Decodable {
    associatedtype DTO: Decodable
    init(dto: DTO) throws
}

extension DTODecodable {
    init(from decoder: Decoder) throws {
        let dto = try DTO(from: decoder)
        self = try Self.init(dto: dto)
    }
}

let json = """
{ "name": "Tanaka", "age": "26", "gender": "0" }
""".data(using: .utf8)!

enum Gender: Int {
    case male = 0
    case female
    case unknown
}

struct Person {
    let name: String
    let age: Int
    let gender: Gender
    
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case gender
    }
}

extension Person: DTODecodable {
    struct DTO: Decodable {
        let name: String
        let age: String
        let gender: String
    }
    
    init(dto: DTO) throws {
        self.name = dto.name
        guard let age = Int(dto.age) else {
            throw DTODecodableError.ageError
        }
        
        guard let genderValue = Int(dto.gender),
              let gender = Gender(rawValue: genderValue) else {
            throw DTODecodableError.genderError
        }
        
        self.age = age
        self.gender = gender
    }
    
    enum DTODecodableError: Error {
        case ageError
        case genderError
    }
}

let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: json)
```


- 実装イメージ

![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63985/5aa9b5bd-dd5b-199a-fbc6-ec62cbf0ec08.png)

## Codableのコードを生成する
- ここでは２つのツールについて紹介します。２つともJSONを元にCodableの実装を生成するツールです。

- [quicktype](https://app.quicktype.io)
    - WebでJSONからCodableの実装が生成されます
- [YutoMizutani/JSONtoCodable](https://github.com/YutoMizutani/JSONtoCodable)
    - JSONtoCodableを使うとCodableの実装が生成されます

![Kapture 2020-12-04 at 18.21.51.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63985/48f48b37-c9ee-f036-de36-ed1f607dd225.gif)

## まとめ
- DTOを使うことでCodableの可読性があがります。
- 生成ツールを使うとさらに爆速で開発できます

## 参考リンク
- https://medium.com/@rjchatfield/swift-4-decodable-2ca8645ce4