SwiftアプリケーションにおいてJSON文字列で作業をする（エンコーディングとデコーディング）

この記事の内容は:
- （エンコーディング）`Codable` を使って `Swift` オブジェクトを JSON文字列にエンコードする
- （デコーディング） JSON文字列を `JSONDecoder()` でデコードする
- （デコーディング）`SwiftyJSON` を使って JSON文字列をデコードする

例では、仮に以下のプログラムの定義があるとしましょう

```swift
struct CatInformation {
    let id: Int
    let name: String
    let toys: [String]
    let status: CatStatus
}
```

```swift
struct CatStatus {
    let happy: Bool
    let playing: Bool
}
```

# オブジェクトからJSON文字列を生成

我々は `Swift` オブジェクトCatInformationをJSON文字列に変換したいと考えています。

以下は `Swift` オブジェクトを定義したコードです：

```swift
let status = CatStatus(happy: true, playing: true)
let catInfo = CatInformation(id: 1, name: "ネコノヒー", toys: ["ペット小屋", "ぐるぐるタワー"], status: status)
```

## 構造体 `Codable` を作る

プログラム構造体の定義に `Codable` プロパティを追加することから始めます

```swift
struct CatInformation: Codable {
    let id: Int
    let name: String
    let toys: [String]
    let status: CatStatus
}

struct CatStatus: Codable {
    let happy: Bool
    let playing: Bool
}
```

## JSONへの変換

```swift
do {
    let encoder = JSONEncoder()
    encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
    let jsonData = try encoder.encode(catInfo)
    let jsonString = String(data: jsonData, encoding: .utf8)
    print(jsonString)
} catch {
    print(error.localizedDescription)
}
```

## そして、出力されたJSON文字列は以下の通りです

```json
"{\n  \"status\" : {\n    \"happy\" : true,\n    \"playing\" : true\n  },\n  \"id\" : 1,\n  \"name\" : \"ネコノヒー\",\n  \"toys\" : [\n    \"ペット小屋\",\n    \"ぐるぐるタワー\"\n  ]\n}"
```

# JSONデコーディング

この例では、以下のJSON文字列をウェブサイトのAPIコールから受け取ったとします。

```json
{
    "id": 1,
    "name": "ネコノヒー",
    "toys": ["ペット小屋", "ぐるぐるタワー"],
    "status": {
        "happy": true,
        "playing": true
    }
}
```

この文字列をSwiftでどのようにデコードしますか。この記事では、このJSON文字列をデコードする2つの方法について考えます。
1. 新しい構造体を定義し、AppleのJSONDecoder（）を使う
2. 既存の構造体を用い、AppleのJSONDecoder（）を使う
2. SwiftyJSONのようなオープンソースフレームワークを使う

## 方法1　新しい構造体でJSONDecoder()を使用する（推奨される方法で簡単）

構造体を作成することができます。構造体はJSONファイル内のものと同じ変数名を含んでいなければなりません。例えば、JSONファイル内に `id`, `name`, `toys`, `status` があるとします。その場合、構造体の中の変数を `id`, `name`, `toys`, `status` と名付けなければなりません。

```swift
struct CatInformation: Decodable {
    let id: Int
    let name: String
    let toys: [String]
    let status: CatStatus
}
```

`status` は入れ子構造の変数(辞書型JSON)なので、それ用の別のプログラム構造を作る必要があります。

```swift
struct CatStatus: Decodable {
    let happy: Bool
    let playing: Bool
}
```

これでJSONメッセージをデコードすることができます

```swift
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(CatInformation.self, from: jsonData)
print(catInfo)
```

そしてオブジェクトの配列をデコードします

```swift
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(Array<CatInformation>.self, from: jsonData)
print(catInfo)
```

出力データは以下の通りです

```
[
 JSONdecode.CatInformation(catID: 1, catName: "ネコノヒー", catToys: ["ペット小屋", "ぐるぐるタワー"], catIsHappy: true, catIsPlaying: true),
 JSONdecode.CatInformation(catID: 2, catName: "ムギ", catToys: ["ぬいぐるみ"], catIsHappy: true, catIsPlaying: true),
 JSONdecode.CatInformation(catID: 3, catName: "レオ", catToys: [], catIsHappy: true, catIsPlaying: false)
]
```

上記のコードを使ってJSON文字列をデコードしてください。最適なコードのはずです。

既存の構造体に `JSONDecoder` 関数を使いたい方、オープンソースのソリューションを使いたい方は続きをご覧ください。

## 方法2　デコード可能な機能を既存の構成に追加する

上記の最初の例では、JSON変数の名前が `Swift` 変数に自動的にマッピングされます。ただし、名前が異なる場合は、 `CodingKey` プロパティを追加できます。

以下が既存の構成です：

```swift
struct CatInformation {
    let catID: Int
    let catName: String
    let catToys: [String]
    let catIsHappy: Bool
    let catIsPlaying: Bool
}
```

### 最初に、クラス/構成にデコード可能なプロパティを追加します

```swift
struct CatInformation: Decodable {
    ...
}
```

### デコードキーを指定します

次に、JSON変数名を指定します。

```swift
struct CatInformation: Decodable {
    ...
    enum CatInformationKeys: String, CodingKey {
        case catID = "id"
        case catName = "name"
        case catToys = "toys"
        case catStatus = "status"
    }
    ...
}
```

これで、JSON文字列から、"happy" および "playing" プロパティが "status" 変数（ネストされた変数）内に格納されていることがわかります。したがって、 "happy" と "playing" を CodingKey の追加セットとして指定する必要があります。

```swift
struct CatInformation: Decodable {
    ...
    enum CatStatusKeys: String, CodingKey {
        case isHappy = "happy"
        case isPlaying = "playing"
    }
    ...
}
```

### `CatInformation` を初期化します

まずは初期化関数を使ってみましょう

```swift
struct CatInformation: Decodable {
    ...
    init(from decoder: Decoder) throws {
        ...
    }
}
```

init関数内：

#### まず、JSON文字列全体をデコードします：

```swift
let values = try decoder.container(keyedBy: CatInformationKeys.self)
```

#### 次に、入れ子になっていない変数をデコードします： `catID`, `catName`, `catToys`

```swift
catID = try values.decode(Int.self, forKey: .catID)
catName = try values.decode(String.self, forKey: .catName)
catToys = try values.decode([String].self, forKey: .catToys)
```

#### 次に、`nestedContainer(keyedBy:forKey:)` を使用してステータスをデコードします

https://developer.apple.com/documentation/swift/keyeddecodingcontainer/2893204-nestedcontainer

```swift
let status = try values.nestedContainer(keyedBy: CatStatusKeys.self, forKey: .catStatus)
catIsHappy = try status.decode(Bool.self, forKey: .isHappy)
catIsPlaying = try status.decode(Bool.self, forKey: .isPlaying)
```

これでinit関数のコーディングは完了です。これでJSON文字列をデコードすることができます：

これがJSON文字列です：

```swift
let singleCatString =
"""
{
    "id": 1,
    "name": "ネコノヒー",
    "toys": ["ペット小屋", "ぐるぐるタワー"],
    "status": {
        "happy": true,
        "playing": true
    }
}
"""
```

これがデコード作業のコードです

```swift
let jsonData = singleCatString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(CatInformation.self, from: jsonData)
print(catInfo)
```

通常、JSON 文字列は、このようなオブジェクトの配列を含んでいます：

```swift
let jsonString =
"""
[
    {
        "id": 1,
        "name": "ネコノヒー",
        "toys": ["ペット小屋", "ぐるぐるタワー"],
        "status": {
            "happy": true,
            "playing": true
        }
    },
    {
        "id": 2,
        "name": "ムギ",
        "toys": ["ぬいぐるみ"],
        "status": {
            "happy": true,
            "playing": true
        }
    },
    {
        "id": 3,
        "name": "レオ",
        "toys": [],
        "status": {
            "happy": true,
            "playing": false
        }
    },
]
"""
```

これで上記のJSON文字列をデコードすることができます：

```swift
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(Array<CatInformation>.self, from: jsonData)
print(catInfo)
```

以下はプログラムの出力です

```
[
 JSONdecode.CatInformation(catID: 1, catName: "ネコノヒー", catToys: ["ペット小屋", "ぐるぐるタワー"], catIsHappy: true, catIsPlaying: true),
 JSONdecode.CatInformation(catID: 2, catName: "ムギ", catToys: ["ぬいぐるみ"], catIsHappy: true, catIsPlaying: true),
 JSONdecode.CatInformation(catID: 3, catName: "レオ", catToys: [], catIsHappy: true, catIsPlaying: false)
]
```

## 方法3 SwiftyJSON

`SwiftyJSON` は、JSONデータのデコードを支援するためのGithub上のオープンソースフレームワークです。

### インストール方法

https://github.com/SwiftyJSON/SwiftyJSON#integration

### SwiftyJSON

```swift
import SwiftyJSON
```

```swift
let data = jsonString.data(using: .utf8)!
let json = JSON(data)
if let catArrays = json.array {
    for catInformation in catArrays {
        if let catDictionary = catInformation.dictionary {
            let id = catDictionary["id"]?.int
            let name = catDictionary["name"]?.string
            let toys = catDictionary["toys"]?.array as? [String]
            //Decode the status
            if let status = catDictionary["status"]?.dictionary {
                let isHappy = status["happy"]?.bool
                let isPlaying = status["playing"]?.bool
                print("\(id), \(name), \(toys), \(isHappy), \(isPlaying)")
            }
        }
    }
}
```

ここで、`.int` は値を任意の整数にキャストします。`.string` はオプションのStringに値をキャストします。

そして、こちらがプログラムの出力です：:

```
Optional(1), Optional("ネコノヒー"), nil, Optional(true), Optional(true)
Optional(2), Optional("ムギ"), nil, Optional(true), Optional(true)
Optional(3), Optional("レオ"), Optional([]), Optional(true), Optional(false)
```

---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">