

# 問題

以下のurlでレビューと電話番号のデータを取ろうとしたのですが、電話番号とレビューの`text`,`rating`以外のほとんどの値がnilになってしまいます。ただ、urlを直接webで叩くと意図したjsonのデータが確認できます。  
`https://maps.googleapis.com/maps/api/place/details/json?place_id="PLACE
ID"&fields=reviews,formatted_phone_number&key="api key"`  
[リファレンス](https://developers.google.com/places/web-
service/details)には、取れていないデータの説明書きの最後にif
available.と記載されているのですが、これが何を意味しているかが分かりません。

取れているデータはあるので文法上のミスないのかなと思います。  
また、Modelはapiを直接叩いて確認したデータを[quicktype](https://app.quicktype.io/)というのを使って生成したのでタイポも考えにくいです。

[code]

    struct Empty: Codable {
        let result: Result
        let status: String?
    }
    
    // MARK: - Result
    struct Result: Codable {
        let formattedPhoneNumber: String?
        let reviews: [Review]
    }
    
    // MARK: - Review
    struct Review: Codable {
        let authorName: String?
        let authorurl: String?
        let language: String?
        let profilePhotourl: String?
        let rating: Int?
        let relativeTimeDescription, text: String?
        let time: Int?
    }
    
[/code]

jsonData (データをそのまんま見せていいのか不安になったので、実際のデータをxに置き換えています）

[code]

    {
       "html_attributions" : [],
       "result" : {
          "formatted_phone_number" : "xx-xxxx-xxxx",
          "reviews" : [
             {
                "author_name" : "xxxx",
                "author_url" : "https://www.google.com/maps/xxxxx/xxxxxxx/reviews",
                "language" : "xx",
                "profile_photo_url" : "https://xxxxx.com/-xxxxxxx/xxxxxx/xxxxxx/xxxxxxx/xxxxxx/photo.jpg",
                "rating" : 4,
                "relative_time_description" : "x か月前",
                "text" : "xxxxxxxxxxxxxx。",
                "time" : 1234567
             },
             {
                "author_name" : "xxxxxxxx",
                "author_url" : "https://www.google.com/maps/contrib/xxxxxxxx/reviews",
                "language" : "xx",
                "profile_photo_url" : "https://lh6.ggpht.com/-xxxxxxx/xxxxxxxx/xxxxxxxxx/xxxxxxxx/xxxxxxx/photo.jpg",
                "rating" : 5,
                "relative_time_description" : "x 週間前",
                "text" : "xxxxxxxxx",
                "time" : 1234567
             }
          ]
       },
       "status" : "OK"
    }
    
[/code]


CC BY-SA 4.0
2020-06-29T15:36:16.860
68118
2020-07-01T15:30:19.183
2020-07-01T09:51:05.513
40865
40865
question
swift google-maps google-cloud google-api
google place api で意図したデータが取れない


jsonData
のご提示ありがとうございます。実際の中身をご提示いただけるまでは、何かの設定やオプションでキーが書き換えられると言う可能性も捨てきれなかったのですが、そんなこともなかったようです。

「text,rating以外のほとんどの値がnil」と言うことですが、現在のあなたのコードを使うと、`language`, `rating`,
`text`, および`time`の各項目は取得できているのではないでしょうか?

これは、

**`Review`クラスのプロパティ名と全く同じキーがJSON中に存在していればデータが取れている**

と言うことを表しています。

一方データが取れていない方の項目ですが、`authorName`を取ってくるために必要な`"authorName"`なんてキーの項目はJSON中には存在しません。

`"authorurl"`も`"profilePhotourl"`も`"relativeTimeDescription"`もJSON中には存在しません。ですから、

**それらの項目が取れないのは当たり前**

と言うことになります。

* * *

## 対応法その1

**モデルのプロパティ名をJSONのキー名に完全に一致させる**

と言うのが一つの方法です。

[code]

    struct PlacesAPIResponse: Codable {
        let result: Result
        let status: String?
    }
    
    // MARK: - Result
    struct Result: Codable {
        let formatted_phone_number: String?
        let reviews: [Review]
    }
    
    // MARK: - Review
    struct Review: Codable {
        let author_name: String?
        let author_url: String?
        let language: String?
        let profile_photo_url: String?
        let rating: Int?
        let relative_time_description, text: String?
        let time: Int?
    }
    
[/code]

(`Empty`なんて型名はあまりにもあれなんで、書き換えています。)

ご紹介いただいたquicktypeサイトでは、この形式のSwiftコードを出力できるようなオプションは見つかりませんでした。他の類似のサイトを探すか、自分で手を入れないといけないでしょう。

ただ、Swiftでは、スネークケースの識別子は原則使用しませんので、コードがSwiftっぽくなくなってしまいます。あまりこの方法を好まれる方はいないかもしれません。

* * *

## 対応方法その2

**`CodingKeys`を明示して、プロパティ名とJSON側のキー名の対応を明記する**

と言う手もあります。

[code]

    import Foundation
    
    // MARK: - PlaceAPIResponse
    struct PlaceAPIResponse: Codable {
        let result: Result?
        let status: String?
    }
    
    // MARK: - Result
    struct Result: Codable {
        let formattedPhoneNumber: String?
        let reviews: [Review]?
    
        enum CodingKeys: String, CodingKey {
            case formattedPhoneNumber = "formatted_phone_number"
            case reviews
        }
    }
    
    // MARK: - Review
    struct Review: Codable {
        let authorName: String?
        let authorURL: String?
        let language: String?
        let profilePhotoURL: String?
        let rating: Int?
        let relativeTimeDescription, text: String?
        let time: Int?
    
        enum CodingKeys: String, CodingKey {
            case authorName = "author_name"
            case authorURL = "author_url"
            case language
            case profilePhotoURL = "profile_photo_url"
            case rating
            case relativeTimeDescription = "relative_time_description"
            case text, time
        }
    }
    
[/code]

このコードはquicktypeサイトで、Explicit CodingKey values in Codable typesと、Make all
properties optionalをONにして生成したものそのままです。

ちなみに自動生成したコードでは`authorURL`や`profilePhotoURL`のように`URL`の部分が大文字に変換されました。ご質問で使用されているコードは手作業で一部変更されたのでしょうか?

なお、quicktypeサイトでそのまま使えそうな`CodingKeys`が生成されたのは、このExplicit CodingKey values in
Codable typesオプションを指定した時だけでした。その他の場合には、使えもしない`CodingKeys`が出力されてしまいます。

ただし、ほとんどの項目名がスネークケースからキャメルケースへの機械的変換で済むのに、`CodingKeys`が指定されているのは、かえってコードが読みにくいと思われる方もいるでしょう。

* * *

# 対応法その3

  * **モデルのプロパティ名をJSONのキー名から機械的に変換したものに完全に一致させる**
  * **`JSONDecoder`で、`keyDecodingStrategy = .convertFromSnakeCase`を指定する**

と言う手もあります。ご質問中には、`JSONDecoder`を使う部分のコードが示されていないので、以下はXcodeのPlaygroundなどで試せる形で記載しておきます。

[code]

    import Foundation
    
    let jsonText = #"""
    {
       "html_attributions" : [],
       "result" : {
          "formatted_phone_number" : "xx-xxxx-xxxx",
          "reviews" : [
             {
                "author_name" : "xxxx",
                "author_url" : "https://www.google.com/maps/xxxxx/xxxxxxx/reviews",
                "language" : "xx",
                "profile_photo_url" : "https://xxxxx.com/-xxxxxxx/xxxxxx/xxxxxx/xxxxxxx/xxxxxx/photo.jpg",
                "rating" : 4,
                "relative_time_description" : "x か月前",
                "text" : "xxxxxxxxxxxxxx。",
                "time" : 1234567
             },
             {
                "author_name" : "xxxxxxxx",
                "author_url" : "https://www.google.com/maps/contrib/xxxxxxxx/reviews",
                "language" : "xx",
                "profile_photo_url" : "https://lh6.ggpht.com/-xxxxxxx/xxxxxxxx/xxxxxxxxx/xxxxxxxx/xxxxxxx/photo.jpg",
                "rating" : 5,
                "relative_time_description" : "x 週間前",
                "text" : "xxxxxxxxx",
                "time" : 1234567
             }
          ]
       },
       "status" : "OK"
    }
    """#
    
    let jsonData = jsonText.data(using: .utf8)!
    
    struct PlacesAPIResponse: Codable {
        let result: Result
        let status: String?
    }
    
    // MARK: - Result
    struct Result: Codable {
        let formattedPhoneNumber: String?
        let reviews: [Review]
    }
    
    // MARK: - Review
    struct Review: Codable {
        let authorName: String?
        let authorUrl: String? //<- `authorurl`や`authorURL`ではダメ
        let language: String?
        let profilePhotoUrl: String? //<- 上記と同様
        let rating: Int?
        let relativeTimeDescription: String?
        let text: String?
        let time: Int?
    }
    
    do {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy =  .convertFromSnakeCase
        let response = try decoder.decode(PlacesAPIResponse.self, from: jsonData)
        print(response)
    } catch {
        print(error)
    }
    
[/code]

`Review`のプロパティ名の一部`authorUrl`と`profilePhotoUrl`、それから`.convertFromSnakeCase`を指定している部分に注意して下さい。

* * *

対応法2の方は、`JSONDecoder`が理解できる変換規則に縛られずに自由にプロパティ名とJSONのキー名の対応が付けられるので、より自由度が高いと言えるでしょう。

ただ、このAPIのように綺麗にキャメルケースに変換できるキー名になっている場合は対応法3を好まれる人も多いかもしれません。

ご自身のコーディングスタイルに合わせて、どれかの方法を試してみて下さい。


CC BY-SA 4.0
2020-07-01T15:30:19.183
68191
2020-07-01T15:30:19.183
13972
68118
answer