【Swift】Codable(Decodable)を使ってみた

## Codableとは
Swift 4で導入されたCodable、主にJSONのエンコードやデコードをする時に使うプロトコルです。
JSON以外にも独自でカスタマイズしてデコードもできるようです。

## Codableの使い方
詳しい説明は省略させていただきます。
・基本形

```swift:pattern1.swift
    let json = """
    {
        "id": 1,
        "title": "タイトル",
        "message": "メッセージ",
    }
    """.data(using: .utf8)!

    func decode() {
        do {
            let hoge = try JSONDecoder().decode(CodableTest.self, from: json)
        } catch {
            print(error)
        }
    }

struct CodableTest: Decodable {
    var id: Int?
    var title: String?
    var message: String?
}
```
簡単な構造のJSONの場合はこれだけでデコードすることができます。
取得したデータはhoge.id等で取得することができます。
structでなくclassでも使うことができます。

・Jsonのキーがスネークケースの場合など

```swift:pattern2.swift
    let json = """
    {
        "items": [
            {
                "id": 1,
                "title": "タイトル1",
                "message": "メッセージ1",
            },
            {
                "id": 2,
                "title": "タイトル2",
                "message": "メッセージ2",
            }
        ],
        "total_count": 2,
    }
    """.data(using: .utf8)!

    func decode() {
        do {
            let hoge = try JSONDecoder().decode(CodableTest.self, from: json)
        } catch {
            print(error)
        }
    }

struct CodableTest {

    struct Content {
        var id: Int?
        var title: String?
        var message: String?
    }

    private enum RootKeys: String, CodingKey {
        case items
        case totalCount = "total_count"
    }

    private enum ItemsKeys: String, CodingKey {
        case id
        case title
        case message
    }

    var contents: [Content]?
    var totalCount: Int = 0
}

extension CodableTest: Decodable {

    init(from decoder: Decoder) throws {
        self.contents = []
        let root = try decoder.container(keyedBy: RootKeys.self)
        var items = try root.nestedUnkeyedContainer(forKey: .items)

        while !items.isAtEnd {
            let container = try items.nestedContainer(keyedBy: ItemsKeys.self)
            var content = Content()
            content.id = try container.decode(Int.self, forKey: .id)
            content.title = try container.decode(String.self, forKey: .title)
            content.message = try container.decode(String.self, forKey: .message)
            self.contents?.append(content)
        }
        totalCount = try root.decode(Int.self, forKey: .totalCount)
    }
}
```
次は少し現実的？なAPIを想定した場合です。
記述が増えましたが、JSONのキーがスネークケースや保存する変数名を変更する場合はenumでJSONのキーを指定してあげます。
配列の場合はfor文等が使えないためwhile文を使用します。

・nullが帰ってきたりキーが存在しない場合

```pattern3.swift
    let json = """
    {
        "items": [
            {
                "id": 1,
                "title": "タイトル1",
                "message": "メッセージ1",
            },
            {
                "id": 2,
                "title": null,
            },
            {
                "id": 3,
                "title": "タイトル3",
                "message": "メッセージ3",
            }
        ],
        "total_count": 2,
    }
    """.data(using: .utf8)!

    func decode() {
        do {
            let hoge = try JSONDecoder().decode(CodableTest.self, from: json)
        } catch {
            print(error)
        }
    }

struct CodableTest {
//上と同じ為、省略
}

extension CodableTest: Decodable {

    init(from decoder: Decoder) throws {
        self.contents = []
        let root = try decoder.container(keyedBy: RootKeys.self)
        var items = try root.nestedUnkeyedContainer(forKey: .items)

        while !items.isAtEnd {
            let container = try items.nestedContainer(keyedBy: ItemsKeys.self)
            var content = Content()
            content.id = try container.decode(Int.self, forKey: .id)
            do {
                content.title = try container.decode(String.self, forKey: .title)
            } catch {
                print(error)
            }
            do {
                content.message = try container.decode(String.self, forKey: .message)
            } catch {
                print(error)
            }
            self.contents?.append(content)
        }
        totalCount = try root.decode(Int.self, forKey: .totalCount)
    }
}
```
配列の途中でnullが入っていたりキーが存在しない場合に、perrern2の記述だとdecode()のキャッチに飛ぶのでwhile文の途中で終了してしまい最後まで処理が行われないので、do catchを入れています。

do catchでなくif文で判定することもできます。

```swift
    if container.contains(./*key*/) {
        // keyが存在している場合
    }
    if try !container.decodeNil(forKey: ./*key*/) {
        // keyがnullではない場合
    }
```

## まとめ
JSONSerializationに比べるとキーがenumなので補完が出るようになっているので良くなったと思います。
簡単なデコードならJSONSerializationより楽にできるようになりました。
ただ、情報がまだ少ないため、ネストや配列の場合にどうやるのか理解するのに時間がかかったので、まだライブラリの方が情報がたくさんあり、わかりやすいかなと思いました。