【Swift】JSONをデコードして使う

![macOS: 12.3.1（21E258）](https://img.shields.io/badge/macOS-12.3.1(21E258)-brightgreen) ![Swift: 5.6](https://img.shields.io/badge/Swift-5.6-brightgreen) ![Xcode: 13.3.1 (13E500a)](https://img.shields.io/badge/Xcode-13.3.1(13E500a)-brightgreen)

## はじめに
今回は、JSONをデコードして使う方法を学んだので備忘録として残します。
私もそうですが、初学者の方には役立つ内容になっていると思いますので、
よかったら見ていってください。

## JSONをデコードして使うまでの流れ
ざっくりと流れは下記のようになります。
1. JSONのデータ構造を確認する。
2. データ構造をSwiftの型として表現する。（構造体の定義）
3. デコード処理をする。

今回は、Playgroundを使います。
▼ ディレクトリ構成 ▼
![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/bdde0142-e31d-da88-70d1-3db3941ce58d.jpeg)
では、やっていきましょう。

## 1. JSONのデータ構造を確認する
まずは中身を確認します。
今回はサンプルとして、下記のJSONを使用します。

```json: Sample.json 
{
    "id": 196,
    "name": "espeon",
    "types": [
        {
            "slot": 1,
            "name": "psychic"
        }
    ],
    "abilities": [
        {
            "name": "synchronize",
            "is_hidden": false,
            "slot": 1
        },
        {
            "name": "magic-bounce",
            "is_hidden": true,
            "slot": 3
        }
    ],
    "stats": [
        {
            "base_stat": 65,
            "name": "hp"
        },
        {
            "base_stat": 65,
            "name": "attack"
        },
        {
            "base_stat": 60,
            "name": "defense",
        },
        {
            "base_stat": 130,
            "name": "special-attack"
        },
        {
            "base_stat": 95,
            "name": "special-defense"
        },
        {
            "base_stat": 110,
            "name": "speed"
        }
    ]
}

```
JSONは、`{}`の中に`"key"`と`value`を`:`で区切って記述されています。
これをSwiftの型で表現するには、`{}`を一つのまとまりとして、構造体で表現し、`"key"`はプロパティで`value`はその値になります。

## 2. Swiftの型として表現する

#### PokemonType型を定義

`types`を見てみましょう。
`[{要素}]`といった形になっています。これは配列で表現できます。
この`{要素}`を`PokemonType`型として下記の様に定義します。

```swift: PokemonType.swift
public struct PokemonType {
    public var slot: Int
    public var name: String
}
```

また、`JSONDcoder`クラスで取り扱いができるように、`Decodable`プロトコルに準拠させます。

```swift: PokemonType.swift
public struct PokemonType: Decodable { //追加
    public var slot: Int
    public var name: String
}
```

これは無くても良いですが、最後に結果を確認しやすいように`CustomStringConvertible`にも準拠しておきます。

```swift: PokemonType.swift
public struct PokemonType: Decodable, CustomStringConvertible { //追加
    public var slot: Int
    public var name: String
    
    //追加
    public var description: String {
        return "\(slot): \(name)"
    }
}
```
#### Ability型を定義

同様に`abilities`プロパティが持つ、配列の要素を`Ability`型として定義します。
```swift: Ability.swift
public struct Ability: Decodable, CustomStringConvertible {
    public var name: String
    public var isHidden: Bool
    public var slot: Int

    public var description: String {
        return "\(slot): \(name)\(isHidden ? "(夢)" : "(通常)")"
    }
}
```

##### キー名とプロパティ名が一致しない場合

上記の様に、JSONのキー名`"is_hidden"`とプロパティ名`isHidden`が一致しない場合があります。
このままではデコードができません。
こういった場合は、列挙型`CodingKeys`をネスト型として定義することで対応します。
下記のようにローバリュー値にキーを設定することでデコード可能になります。

```swift: Ability.swift
public struct Ability: Decodable, CustomStringConvertible {
    public var name: String
    public var isHidden: Bool
    public var slot: Int
    
    //追加
    public enum CodingKeys: String, CodingKey {
        case name
        case isHidden = "is_hidden"
        case slot
    }
    
    public var description: String {
        return "\(slot): \(name)\(isHidden ? "(夢)" : "(通常)")"
    }
}
```

#### Pokemon型を定義

最後にこのJSONを`Pokemon`型として定義します。

```swift: Pokemon.swift
public struct Pokemon: Decodable, CustomStringConvertible {
    public var id: Int
    public var name: String
    public var types: [PokemonType]
    public var abilities: [Ability]
    
    public var description: String {
        return """
        全国図鑑No: \(id)
        ポケモン: \(name)
        タイプ: \(types)
        特性: \(abilities)
        """
    }
}
```
これでJSONをSwiftの型として表現できました。

## 3. デコード処理をする
最後に作成した`Pokemon`型を使ってデコードをします。

デコードには`Foundation`で提供されている`JSONDecoder`クラスを使用します。
下記のように記述するとデコードができます。

```swift: Sample.playground
import Foundation

let decoder = JSONDecoder()
//Sample.jsonのパスを取得
let path = Bundle.main.url(forResource: "Sample", withExtension: "json")!
do {
    //JSONの読み込み
    let data = try Data(contentsOf: path)
    //デコード
    let pokemon = try decoder.decode(Pokemon.self, from: data)
    print(pokemon)
} catch {
    //エラーが発生した場合
    print(error)
}
```

##### decode(_:from:)メソッド
公式ドキュメントより、下記のように宣言されてることが確認できます。

> func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable

- `type`: JSONを表す型。（制約: Decodableプロトコルへの準拠）
- `data`: デコード対象のデータ

#### 実行結果
```
全国図鑑No: 196
ポケモン: espeon
タイプ: [1: psychic]
特性: [1: synchronize(通常), 3: magic-bounce(夢)]
```
ちゃんとデコードできました。

## さいごに
少し長くなりましたが、JSONのデコードして使う方法をまとめてみました。
参考にしていただければ幸いです。
（独学素人なので、おかしなところがあればご指摘をお願いします。）

## 参考記事・書籍

https://rusutikaa.github.io/docs/developer.apple.com/documentation/foundation/propertylistdecoder/2895397-decode.html

https://gihyo.jp/book/2020/978-4-297-11213-4

https://www.sbcr.jp/product/4815604073/