Takahiro Octopress Blog

-1から始める情弱プログラミング

ぐるなびレストラン検索APIで困ったCodable

はじめに

さて、今回は Codable で困った話です。
ぐるなびレストラン検索APIを利用する機会がありまして、
どうせなら Codable を使ってキレイに書こうと思ったが…という話になります。

ぐるなびレストラン検索APIのレスポンスについて

ほとんどの場合は何も考えずに Codable が適用できたのですが、image_urlだけ困りました。
と言うのも返却されるレスポンスに以下の違いがあったためです。

実際にAPIを叩いて頂くと…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 値がある場合
image_url {
    shop_image1: http\:\/\/xxxxxxx,
    shop_image2: http\:\/\/xxxxxxx,
    qrcode: http\:\/\/xxxxxxx
}

// 値がない場合
image_url {
    shop_image1: {
    },
    shop_image2: {
    },
    qrcode: http\:\/\/xxxxxxx
}

といった感じになっていることがわかると思います。

Codableをどこまで利用するべきか

筆者が下した結論としては、全てを Codable だけで賄うのは難しいということです。
なぜなら CodableAny 型を扱えないからです。
ではどうすれば良いかと言うと…

  • SwiftyJSONを使う
  • CodableSwiftyJSON を使う

ことが考えられるかと思います。

せっかくなので、CodableSwiftyJSON を両方使ってみた場合を見ていきましょう。

まずCodableの定義です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public struct Restaurant: Codable {

    public var id: String
    public var name: String
    public var nameKana: String
    public var address: String
    public var url: String
    public var tel: String
    public var latitude: String
    public var longitude: String
    public var urlMobile: String
    public var shopImage1: String?
    public var shopImage2: String?
}

public struct Restaurants: Codable {

    public var rest: [Restaurant]
    public var totalHitCount: String
    public var hitPerPage: String
    public var pageOffset: String
}

続いて、SwiftyJSONをどのように使ったかというと…
(以下、処理の抜粋です。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Alamofire.request(requestURL, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: nil)
    .response { response in
        do {
            var result = Restaurants(rest: [], totalHitCount: "", hitPerPage: "", pageOffset: "")

            guard let data = response.data else {
                return
            }
            let rest = JSON(data)["rest"]
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let restaurants = try decoder.decode(Restaurants.self, from: data)
            guard let array = rest.array else {
                completion(restaurants)
                return
            }
            for elem in array {
                for restaurant in restaurants.rest where restaurant.id == elem["id"].string {
                    var convertedRestaurant = restaurant
                    convertedRestaurant.shopImage1 = elem["image_url"]["shop_image1"].string
                    convertedRestaurant.shopImage2 = elem["image_url"]["shop_image2"].string
                    result.rest.append(convertedRestaurant)
                    break
                }
            }

            completion(result)
        } catch {
            print("error")
        }
}

ただ、これはfor文をぶん回して照らし合わせてCodableの形に充てがいたいがために書いているので、処理効率が良いかは別の話ですね…
APIで取得できた件数が少なければ大した話ではありませんが…。
(大量件数が取得されるケースでは、むしろ少数に減らすことができないか検討すべきでしょう。)

まとめ

さて、今回はCodableを利用しようと思って困った話を書いてみました。
今回のAPIの返却値は特殊な感じもするので、今後Swiftが対応していくようになるとは思えないのですが、
もしかしたらもう少し書きやすくなることもあるかもしれません。

逆に、iOSアプリ向けにAPIを新規開発するときは、上記のような事情も踏まえて開発すると困ることが少ないのではと思います。

と言ったところで本日はここまで。

Comments