Swift Codableの公式解説をちょっと補足

Codableについての公式の解説記事はこちらですが、JSONの具体例がなかったり読んでるだけだと若干わかりづらいところがあったので、自分なりに補足をつけてみたいと思います。主にCodingKeys周りを見ていきます。

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types

<br>

# 具体的なJSONで理解する
解説記事のコードをまとめるとこんな感じです。デコード部分に絞ってます。

```swift
struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double

    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }

    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)

        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}
```

<br>

解説記事には出てこないので分かりづらかったのですが、CoordinateはこういうJSONに対応しています。

```json
{
    "latitude": 1.1,
    "longitude": 2.2,
    "additionalInfo": {
        "elevation": 3.3
    }
}
```

<br>

デコードするコードはこんな感じですね。

```swift
let json = """
{
    "latitude": 1.1,
    "longitude": 2.2,
    "additionalInfo": {
        "elevation": 3.3
    }
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)

// 出力結果
// Coordinate(latitude: 1.1, longitude: 2.2)
```

<br>

# CodingKeysという名前でなければいけないのか？
解説にはこのような記載があります。

> Codable types can declare a special nested enumeration named CodingKeys that conforms to the CodingKey protocol.

<br>

上記の例でもCoordinate内部でCodingKeysというenumを宣言していますね。この名前を変えたらどうなるか試してみたいと思います。

まずはCodingKeysという名前で宣言した例です。

```swift
struct Coordinate: Decodable {
    var latitude: Double
    var longitude: Double

    enum CodingKeys: String, CodingKey {
        case latitude = "latitude_test"
        case longitude
    }
}

let json = """
{
    "latitude_test": 1.1,
    "longitude": 2.2,
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)

// 結果
// Coordinate(latitude: 1.1, longitude: 2.2)
```

<br>

次に、CodingKeysをHogeKeysに変えてみます。すると、`latitude`というキーが無いと怒られてしまいました。HogeKeysでは当然ながら認識されないようです。

```swift
struct Coordinate: Decodable {
    var latitude: Double
    var longitude: Double

    enum HogeKeys: String, CodingKey {
        case latitude = "latitude_test"
        case longitude
    }
}

let json = """
{
    "latitude_test": 1.1,
    "longitude": 2.2,
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)

// エラー
// __lldb_expr_14/MyPlayground.playground:37: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "latitude", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"latitude\", intValue: nil) (\"latitude\").", underlyingError: nil))
```

<br>

HogeKeysという名前にする場合は、`init(from:)`を実装する必要があります。

```swift
struct Coordinate {
    var latitude: Double
    var longitude: Double

    enum HogeKeys: String, CodingKey {
        case latitude = "latitude_test"
        case longitude
    }
}

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: HogeKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
    }
}

let json = """
{
    "latitude_test": 1.1,
    "longitude": 2.2,
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)
```

<br>

つまり、CodingKeysという名前にすることで、`init(from:)`を自動で実装してくれるんですね。
さらに、`init(from:)`が具体的にどういうことをやっているのかも最後の例でわかりました。