質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

解決済

1回答

240閲覧

Swift  JSONデータの取得

Family

総合スコア1

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2024/09/03 05:15

編集2024/09/04 10:05

実現したいこと

JSONの取得をし、検索結果を一覧表示できるようにしたい。

発生している問題・分からないこと

デバッグの方でguard let items = json.item else { ... }のブロックで「アイテムが見つかりませんでした」と出力されてしまいます。おそらく、そのためResultJson(item: nil)とJSONデータが帰ってきてしまってるんだと考えています。しかし、どのように修正すべきか分からないです、、、
どうかご教示よろしくお願いいたします。

該当のソースコード

Swift

1import SwiftUI 2 3//Identifiableプロトコルを利用して、お店情報をまとめる構造体 4struct OmiseItem : Identifiable { 5 let id = UUID() 6 let name: String 7 let link: URL 8 let image: URL 9} 10 11//お店データ検索用クラス 12@Observable class OmiseData { 13 //JSONのデータ構造 14 struct ResultJson: Codable { 15 //JSONのitem内のデータ構造 16 struct Item: Codable { 17 //お店の名称 18 let name : String? 19 //掲載URL 20 let url: URL? 21 //画像URL 22 let image: URL? 23 } 24 //複数要素 25 let item: [Item]? 26 }//ResultJson ここまで 27 28 //お店のリスト(Identifiableのプロトコル) 29 var omiseList: [OmiseItem] = [] 30 //クリックされたWebページのURL情報 31 var omiseLink: URL? 32 33 //web API検索用メソッド 第一引数:keyword 検索したいワード 34 func searchOmise(keyword: String) { 35 //デバッグエリアに出力 36 print("searchメソッドで受け取った値:\(keyword)") 37 38 //Taskは非同期で処理を実行できる 39 Task { 40 //ここから先は非同期で処理される 41 //非同期でお菓子を検索する 42 await search(keyword: keyword) 43 }//Taskここまで 44 }//searchOmiseここまで 45 46 //非同期でお店データを取得 47 //@MainActorを使いメインスレッドを更新する 48 @MainActor 49 private func search(keyword: String) async { 50 // お店の検索キーワードをURLエンコードする 51 guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { 52 print("URLエンコードに失敗しました") 53 return 54 } 55 56 // リクエストURLの組み立て 57 guard let req_url = URL(string: "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー取得済みです]&format=json&keyword=\(keyword_encode)&order=1") else { 58 print("リクエストURLの組み立てに失敗しました") 59 return 60 } 61 // デバッグエリアに出力 62 print(req_url) 63 64 do { 65 // リクエストURLからダウンロード 66 let (data, response) = try await URLSession.shared.data(from: req_url) 67 68 // responseがHTTPURLResponse型にキャストできるか確認 69 if let httpResponse = response as? HTTPURLResponse { 70 // HTTPレスポンスステータスコードやレスポンスヘッダーの確認 71 print("HTTPステータスコード: \(httpResponse.statusCode)") 72 print("HTTPヘッダー: \(httpResponse.allHeaderFields)") 73 } 74 75 // 取得したJSONデータを文字列として出力 76 if let jsonString = String(data: data, encoding: .utf8) { 77 print("取得したJSONデータ: \(jsonString)") 78 } 79 80 // JSONDecoderのインスタンスを取得 81 let decoder = JSONDecoder() 82 83 // JSONのパースを別のdo-catchで行う 84 do { 85 let json = try decoder.decode(ResultJson.self, from: data) 86 print("パース成功: \(json)") 87 88 // お店の情報が獲得できているか確認 89 guard let items = json.item else { 90 print("アイテムが見つかりませんでした") 91 return 92 } 93 // お店のリストを初期化 94 omiseList.removeAll() 95 96 // 取得しているお店の数だけ処理 97 for item in items { 98 // お店の名称、掲載URL、画像URLをアンラップ 99 if let name = item.name, 100 let link = item.url, 101 let image = item.image { 102 // 一つのお店を構造体でまとめて管理 103 let omise = OmiseItem(name: name, link: link, image: image) 104 // お店の配列へ追加 105 omiseList.append(omise) 106 } 107 } 108 print(omiseList) 109 } catch { 110 // JSONデコードのエラーをキャッチ 111 print("デコードエラー: \(error)") 112 } 113 114 } catch { 115 print("エラーが出ました") 116 } // do ここまで 117 } // search ここまで 118} 119

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

responseがHTTPURLResponse型にキャストできるか確認すると、HTTPステータスコード: 200と出力されているのでレスポンスヘッダーが正しく取得されていると思います。

補足

修正しました(2回目)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2024/09/03 08:53

> リクエストURLは正常に作成されていて、エラーも出てないのですが、リクエスト生成とJSONの取得がうまくいっていないからだと考えています。 「リクエスト生成とJSONの取得がうまくいっていない」 ちょっとまだ問題が細かく分解できていないのかなと思いました。 * リクエストして想定通りのレスポンスが取得できるかどうか * レスポンスからJSONのパースができるかどうか どちらに問題があるのかご自身でデバッグしながら確認すると良いかなと思いました。 `guard`で`else`の場合に単純に`return`で終了してしまっていますので、 どのように処理がされているのかブレークポイントで止めて順番に確認すると良いと思います。 [Setting breakpoints to pause your running app | Apple Developer Documentation](https://developer.apple.com/documentation/xcode/setting-breakpoints-to-pause-your-running-app) --- 「該当のソースコード」でリクエストしているのは次のAPIだと思います。 [ホットペッパー | APIリファレンス | リクルートWEBサービス](https://webservice.recruit.co.jp/doc/hotpepper/reference.html) APIを利用するには登録が必要みたいですが、登録済みでしょうか? それらの前提も質問欄に記載しておくと良いかなと思います。 *URLに`key=ここにAPIキー入力`のような記述がありましたので、登録していないのかな?と思いました。 *それから、APIリファレンスを見ると、`&max=10&order=r`のmaxのパラメータはないみたいで、orderに"r"もないみたいです。
Family

2024/09/03 17:22

ご教授いただきありがとうございます。 教えていただいた通り、デバッグしながら確認したところJSONデータがnillで帰ってきてしまっていてResultJson(item: nil)となってしまっています。 APIの登録は済んでおり、記述しておらず申し訳ありません。 改めてパラメータを修正しました。
退会済みユーザー

退会済みユーザー

2024/09/04 00:00 編集

> ResultJson(item: nil)とJSONデータが帰ってきてしまっています。 質問の修正ありがとうございます。 > order ソート順 > 検索結果の並び順を指定します。おススメ順は定期的に更新されます。 > ※ 位置検索の場合、「4:オススメ順」以外は指定に関係なく、強制的に距離順でソートされます。 > 1:店名かな順 > 2:ジャンルコード順 > 3:小エリアコード順 > 4:おススメ順 > 初期値はおススメ順。位置から検索を行った場合は距離順 > [ホットペッパー | APIリファレンス | リクルートWEBサービス](https://webservice.recruit.co.jp/doc/hotpepper/reference.html) orderに20はなくて、1〜4のいずれかになるかなと思います。 --- > keyword キーワード > 店名かな、店名、住所、駅名、お店ジャンルキャッチ、キャッチのフリーワード検索(部分一致)が可能です。文字コードはUTF8。半角スペース区切りの文字列を渡すことでAND検索になる。複数指定可能*2 > [ホットペッパー | APIリファレンス | リクルートWEBサービス](https://webservice.recruit.co.jp/doc/hotpepper/reference.html) keywordですが、UTF8で半角スペース区切りで複数指定可能とのことですので、 PercentEncodingが必要なのかな?と思ったところです。 問題の全部をプログラムからだけ確認していくのも手間になってしまうと思いますので、 次の2つのURLをブラウザーのアドレスバーに貼り付けて、 想定通りのJSONのレスポンスが得られるか試してみると良いかなと思いました。 *keyには取得済みのAPIキーを記述してください https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキーは取得済みです]&format=json&keyword=%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%81%AE%20%E6%96%87%E5%AD%97%E5%88%97%E3%81%A7%E3%81%99%E3%80%82&order=1 https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキーは取得済みです]&format=json&keyword=日本語の 文字列です。&order=1 --- `let (data, _) = try await URLSession.shared.data(from: req_url)` 破棄してしまっている2つ目の戻り値にレスポンスの内容がありますので、 HTTPレスポンスステータスコードやレスポンスのHTTPヘッダーの内容も確認すると良いと思いました。 *ブラウザーの開発ツールからもレスポンスのHTTPヘッダーなどの内容が確認できると思います。(まずはこちらですかね。) [data(from:delegate:) | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/urlsession/3767353-data)
Family

2024/09/04 10:20

お世話になっております。 orderのところのご指摘ありがとうございます。修正しました。 ブラウザーのアドレスバーにURLを貼ったところ、店舗情報のあるJSONデータがレスポンスとして表示されました。 HTTPレスポンスステータスコードやレスポンスのHTTPヘッダーはHTTPステータスコード: 200とデバッグで確認できました。 しかし「アイテムが見つかりませんでした」とデバッグで表示されることから、取得したJSONデータが期待している構造(ResultJson構造体)と一致していない可能性があるかもしれないです。
guest

回答1

0

ベストアンサー

しかし「アイテムが見つかりませんでした」とデバッグで表示されることから、取得したJSONデータが期待している構造(ResultJson構造体)と一致していない可能性があるかもしれないです。

コードの修正ありがとうございます。

77行目のJSONの文字列は想定通りのものが出力されているということですよね。
print("取得したJSONデータ: \(jsonString)")
*resultsの中にshopの配列があるということですよね
*ちょっと実際のレスポンスのJSONを見ていなくて、APIリファレンスも見方がよくわからなくて・・

JSONのオブジェクトの階層に合わせてstructを作ると良いと思います。
プロパティの名前もJSONのプロパティに合わせると良いと思います。
ちょっと勝手な想定ですが、下のコードはサンプルのJSONに合わせてstructを作っています。
階層のイメージがつきますでしょうか?

swift

1// サンプルのJSON 2let json = """ 3{ 4 "results": { 5 "api_version": 1.20, 6 "results_available": 47, 7 "results_returned": 47, 8 "results_start": 1, 9 "shop": [ 10 { 11 "id": "J999999999", 12 "name": "居酒屋 ホットペッパー", 13 "logo_image": "https://logo.domain.com/logo.png", 14 "urls": { 15 "pc": "https://pc.domain.com/pc.html" 16 } 17 } 18 ] 19 } 20} 21""" 22struct Response: Codable { 23 let results: Results 24} 25struct Results: Codable { 26 let api_version: Double 27 let results_available: Int 28 let results_returned: Int 29 let results_start: Int 30 let shop: [Shop] 31} 32struct Shop: Codable { 33 let id: String 34 let name: String 35 let logo_image: String 36 let urls: Urls 37} 38struct Urls: Codable { 39 let pc: String 40} 41let obj: Response? = try? JSONDecoder().decode(Response.self, from: json.data(using: .utf8)!) 42print(obj?.results.shop[0].name ?? "nil") // "居酒屋 ホットペッパー\n" 43print(obj?.results.shop[0].urls.pc ?? "nil") // "https://pc.domain.com/pc.html\n" 44print(obj?.results.shop[0].logo_image ?? "nil") // "https://logo.domain.com/logo.png\n"

投稿2024/09/04 10:50

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Family

2024/09/05 06:20

yametai様、お世話になっております。 サンプルのJSONに合わせてstructを作ったところ、うまくいきました。 本当にありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問