APIから受け取ったJSONデータをCodableプロトコルで構造体にマッピングする

先日はじめてJSONデータをちゃんとパースする書き方をしたので、まとめます。

# 構造体を使わずに無理やりキャストする例
Codableプロトコルを使わなくても、やる方法はあります。
相当な力技なんですが、二ヶ月前くらいに処理したときはこれでやりました。

[Swift3でJSONパースを行う](https://qiita.com/suzuki_y/items/1b64a116ee3c6c9c2805)
とかを参考にして、無理やりキャストしたのが、下記です。

```swift:JSONSerializationでやった例
func getNumberOfPhotos(url: URL) -> Int {
        
        var request = URLRequest(url: url)
        var returnnumberOfPhotos = 0
        let semaphore = DispatchSemaphore(value: 0)
        request.httpMethod = "GET"
        
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            guard let data = data else { return }
            do {
                let dictinaryForJSON = try JSONSerialization.jsonObject(with: data, options: []) as! Dictionary<String, Any>
                let dictionaryForJSONBydata = dictinaryForJSON["data"]! as! Dictionary<String, Any>
                let dictionaryForJSONBydataBycounts = dictionaryForJSONBydata["counts"]! as! Dictionary<String, Int>
                returnnumberOfPhotos = dictionaryForJSONBydataBycounts["media"]!
            } catch let e {
                print(e)
            }
        }
        task.resume()
        return returnnumberOfPhotos
    }
```
**うん、汚いですね。**
可読性めちゃくちゃ悪いな……とは当時から思ってました。

InstagramのAPIが返してくる、下記のJSONデータをparseしたかったのが目的でした。

```
{
  "data": {
    "id": "1574083",
    "username": "snoopdogg",
    "full_name": "Snoop Dogg",
    "profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
    "bio": "This is my bio",
    "website": "http://snoopdogg.com",
    "is_business": false,
    "counts": {
      "media": 1320,
      "follows": 420,
      "followed_by": 3410
    }
  }
}
```
[User Endpoints](https://www.instagram.com/developer/endpoints/users/)

初心者過ぎて構造体のマッピングの方法がわからずに、こんなやり方をしましたが、この方法をとるメリットはないですね。

# 構造体にマッピングする例
APIと通信する際は、アプリ側でも受け取る構造体を定義しましょう。
[Githubのユーザー検索API](https://developer.github.com/v3/search/#search-users)だと、

```
{
  "total_count": 12,
  "incomplete_results": false,
  "items": [
    {
      "login": "mojombo",
      "id": 1,
      "node_id": "MDQ6VXNlcjE=",
      "avatar_url": "https://secure.gravatar.com/avatar/25c7c18223fb42a4c6ae1c8db6f50f9b?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png",
      "gravatar_id": "",
      "url": "https://api.github.com/users/mojombo",
      "html_url": "https://github.com/mojombo",
      "followers_url": "https://api.github.com/users/mojombo/followers",
      "subscriptions_url": "https://api.github.com/users/mojombo/subscriptions",
      "organizations_url": "https://api.github.com/users/mojombo/orgs",
      "repos_url": "https://api.github.com/users/mojombo/repos",
      "received_events_url": "https://api.github.com/users/mojombo/received_events",
      "type": "User",
      "score": 105.47857
    }
  ]
}
```
こんなJSONオブジェクトを返してきます。
これをSwiftで書いてやると、

```
struct User: Codable {
    let total_count: Int
    let incomplete_results: Bool
    let items: [Item]
    
    struct Item: Codable {
        let login: String
        let id: Int
        let node_id: String
        let avatar_url: URL
        let gravatar_id: String?
        let url: URL
        let html_url: URL
        let followers_url: URL
        let subscriptions_url: URL
        let organizations_url: URL
        let repos_url: URL
        let received_events_url: URL
        let type: String
        let score: Double
    }
}
```
こんな感じになります。
Codableプロトコルに準拠させるのがポイントです。
もしもiOSアプリで必要な情報が限られて、絞りたいのであれば、
構造体の中でその変数だけ用意してやればOKです。
ただし、構造体で定義した変数がAPIから返されたデータの中に入っていないと、parseに失敗します。

```swift:Codableでやった例
    func searchGithubUser(query: String) -> User {
        
        let url = URL(string: "https://api.github.com/search/users?q=" + query)!
        var request = URLRequest(url: url)
        var rowData = Data()
        let semaphore = DispatchSemaphore(value: 0)
        let decoder: JSONDecoder = JSONDecoder()
        request.httpMethod = "GET"
    
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            guard let data = data else { return }
            rowData = data
            semaphore.signal()
        }
        task.resume()
        _ = semaphore.wait(timeout: DispatchTime.distantFuture)
    
        do {
            let user: User = try decoder.decode(User.self, from: rowData)
            return user
        } catch let e {
            print("JSON Decode Error :\(e)")
            fatalError()
        }
    }
```
関数の呼び出し元で検索クエリを入れてもらい、該当するユーザーIDと先頭が一致するユーザーの情報が複数APIから返ってきます。
最初素人考えで、複数返してくるのだから、ループ文書かないとダメなのか？　と思っていましたが、
構造体でItemをネストした構造体として、Itemsという配列の一要素にしているので、
上記のコードで上手いことやってくれます。
Codableすごい！

無理やりキャストしていた例だと、必要な値にたどりつくまで、
地獄みたいな段階を踏んでいましたが、この関数で返しているUserを使うと、シンプルに書けます。

```swift:構造体から値を抜き出す
class ViewModel {
    var logins = [String]()
    //~~~~~~~中略~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    func setUserInformation(user: User) -> () {
        logins = user.items.map { $0.login }
    }
```