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

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

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

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

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Swift

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

Q&A

解決済

1回答

4753閲覧

【swift】受け取ったJSONデータをパースできない(もしくは格納できていない)

Tajiko

総合スコア12

JSON

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

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Swift

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

0グッド

0クリップ

投稿2019/02/21 09:02

編集2019/02/21 09:51

前提・実現したいこと

iOSアプリを作成しています。
ライブハウスの名前を検索して地図上に周辺のコインロッカーを表示させるアプリです。
場所の情報(検索ワード、経度、緯度、詳細)のデータベースを仮想サーバに作り、
サーバー側にPHPファイルを起いて自作APIを作り、JSON形式で出力するようにしています。

発生している問題・エラーメッセージ

swift側でJSONのパースが上手くできないのか、それとも構造体への格納ができないのか
do try catch エラーハンドリングで必ずエラーをキャッチしてしまいます。

http://192.168.33.10:8000/api.php/?livehouse=%E6%9C%AC%E5%85%AB%E5%B9%A1 2019-02-21 17:41:49.762064+0900 WhereistheCoinLocker[10697:390643] TIC Read Status [1:0x600000ae1380]: 1:57 php output: Optional("[{\"latitude\":35.720825,\"longitude\":139.927428,\"keyword\":\"JR本八幡駅改札付近北口方面\"},{\"latitude\":35.7211,\"longitude\":139.927267,\"keyword\":\"都営新宿線本八幡駅 地下1階 駅長事務室の左右\"},略 検索結果が続きます]") エラーがでました

該当のソースコード

swift

1//jsonの中身を受け取るデータ構造 2 struct Coinlocker: Codable { 3 let latitude : Double? 4 let longitude : Double? 5 let keyword : String? 6 } 7 8 //jsonのデータ構造 9 struct ResultJson: Codable { 10 //複数要素 11 let item:[Coinlocker]? 12 } 13 14 //ロッカーを検索するためPHPにリクエストするメソッド 15 func searchLocker (keyword: String) { 16 17 //検索キーワードをURLエンコードする 18 guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: sNSCharacterSet.urlQueryAllowed) else { 19 return 20 } 21 22 //リクエストURLの組み立て 23 guard let req_url = URL(string: "http://192.168.33.10:8000/api.php/?livehouse=(keyword_encode)") else { 24 return 25 } 26 print(req_url) 27 28 //リクエストに必要な情報を生成 29 let req = URLRequest(url: req_url) 30 31 //データ転送を管理するためのセッションを生成 32 let session = URLSession(configuration: .default, delegate: nil 33 , delegateQueue: OperationQueue.main) 34 //リクエストをタスクとして登録 35 let task = session.dataTask(with: req, completionHandler: { 36 (data , response , error) in 37 38 //セッションを終了 39 session.finishTasksAndInvalidate() 40 //do try catch エラーハンドリング 41 do { 42 //data にちゃんと検索結果が入っているらしい 43 let phpOutput = String(data: data!, encoding: .utf8) 44 print("php output: (String(describing: phpOutput))") 45 46 //JsonDecoderのインスタンス取得 47 let decoder = JSONDecoder() 48 //受け取ったJSONデータをパース(解析)して格納 49 let json = try decoder.decode(ResultJson.self, from: data!) 50 51 print(json) 52 53 } catch { 54 //エラー処理 55 print("エラーがでました") 56 } 57 }) 58 //ダウンロード開始 59 task.resume() 60 }

PHP

1<?php 2 3if(isset($_GET['livehouse'])) { 4 5 $livehouse = htmlspecialchars($_GET['livehouse']); 6 7define("USERNAME","xxxxx"); 8define("PASSWORD", "xxxxxx"); 9 10try{ 11 $dbh = new PDO("mysql:host=192.168.33.10;dbname=coinlockerdb; 12 charset=utf8", USERNAME, PASSWORD); 13 14 $dbh-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 15 $dbh-> setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 16 $stmt = $dbh->prepare("select latitude, longitude, keyword from coinlockers where livehouse like '%$livehouse%'"); 17 $stmt-> setFetchMode(PDO::FETCH_ASSOC); 18 $stmt-> execute(); 19 $rows = array(); 20 while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ 21 $rows[] = $row; 22 } 23 # 接続成功ならjson形式で吐き出す 24 header('Content-Type: application/json; charset=UTF-8'); 25 echo $json = json_encode($rows, JSON_UNESCAPED_UNICODE); 26 return $json; 27} catch (PDOException $e){ 28 echo "JSON吐き出し失敗"; 29 echo $e->getMessage(); 30} 31$dbh =null; 32}else{ 33 echo "失敗"; 34}

試したこと

swift側の構造体の変数名とデータベースの各項目のスペルが間違っていないか確認しました。
デバッグエリアに出ている TIC Read Status [1:0x600000ae1380]: 1:57についても検索してみましたがこれはエラーではないのでしょうか。調べてもよくわかりませんでした。
初心者でどこがおかしいのか、何を調べていいのかもわからない状態です。

補足情報(FW/ツールのバージョンなど)

データベースのテーブルの情報は下記の通りです。
+-----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| latitude | double | NO | UNI | NULL | |
| longitude | double | NO | UNI | NULL | |
| livehouse | varchar(50) | NO | | NULL | |
| keyword | text | NO | | NULL | |
+-----------+------------------+------+-----+---------+----------------+

参考にしたものはこちらです。(たった2日でマスターできるiPhoneアプリ開発集中講座 Xcode 10 Swift 4.2対応 より)

swift

1 //Jsonのitem内のデータ構造 2 struct ItemJson: Codable { 3 //お菓子の名前 4 let name: String? 5 //メーカー 6 let maker: String? 7 //掲載URL 8 let url: URL? 9 //画像のURL 10 let image: URL? 11 } 12 13 //JSONのデータ構造 14 struct ResultJson: Codable { 15 //複数要素 16 let item:[ItemJson]? 17 } 18 19 //searchokashiメソッド 20 func searchOkashi (keyword: String) { 21 22 //お菓子の検索キーワードをURLエンコードする 23 guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { 24 return 25 } 26 27 //リクエストURLの組み立て 28 guard let req_url = URL(string: 29 "http://www.sysbird.jp/toriko/api/?apikey=guest&format=json&keyword=(keyword_encode)&max=10&prder=r") else { 30 return 31 } 32 print(req_url) 33 34 //リクエストに必要な情報を生成 35 let req = URLRequest(url: req_url) 36 //データ転送を管理するためのセッションを生成 37 let session = URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue.main) 38 //リクエストをタスクとして登録 39 let task = session.dataTask(with: req, completionHandler: { 40 (data, response, error) in 41 //セッション終了 42 session.finishTasksAndInvalidate() 43 //do try catcyエラーハンドリング 44 do{ 45 //JSONDecoderのインスタンス取得 46 let decoder = JSONDecoder() 47 //受け取ったJSONデータをパース(解析)して格納 48 let json = try decoder.decode(ResultJson.self, from:data!) 49 50 51 //お菓子の情報が取得できているか確認 52 if let items = json.item { 53 //お菓子のリストを初期化 54 self.okashiList.removeAll() 55 //取得したお菓子の数だけ処理 56 for item in items { 57 //お菓子の名前、メーカ名、掲載URL,画像URLをアンラップ 58 if let name = item.name, let maker = item.maker, let link = item.url, let image = item.image { 59 //1つのお菓子をタプルでまとめて管理 60 let okashi = (name, maker, link, image) 61 //お菓子の配列へ追加 62 self.okashiList.append(okashi) 63 } 64 } 65 //TableView を更新する 66 self.tableView.reloadData() 67 if let okashidbg = self.okashiList.first { 68 print ("-----------------") 69 print ("okashiList[0] = (okashidbg)") 70 } 71 } 72 73 } catch { 74 //エラー処理 75 print("エラーが出ました") 76 } 77 }) 78 //ダウンロード開始 79 task.resume() 80 } 81 82 //Cellに値を設定するdatasourceメソッド。必ず記述。 83 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 84 //お菓子リストの総数 85 return okashiList.count 86 } 87 88 //Cellに値を設定するdatasourceメソッド。必ず記述。 89 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 90 //今回表示を行う、Cellオブジェクト(1行)を取得 91 let cell = tableView.dequeueReusableCell(withIdentifier: "okashiCell", for: indexPath) 92 //お菓子のタイトル設定 93 cell.textLabel?.text = okashiList[indexPath.row].name 94 //お菓子の画像を取得 95 if let imageData = try? Data(contentsOf: okashiList[indexPath.row].image) { 96 //正常に取得できた場合は、UIImageで画像オブジェクトを生成してCellにお菓子画像を設定 97 cell.imageView?.image = UIImage(data: imageData) 98 } 99 //設定済みのCellオブジェクトを画像に反映 100 return cell 101 } 102 103 //Cellが洗濯された際に呼び出されるdelegateメソッド 104 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 105 // ハイライト解除 106 tableView.deselectRow(at: indexPath, animated: true) 107 108 //SFSafariViewを開く 109 let safariViewController = SFSafariViewController(url: okashiList[indexPath.row].link) 110 111 //dekegate の通知先を自分自身 112 safariViewController.delegate = self 113 114 //SafariViewが開かれる 115 present(safariViewController, animated: true, completion: nil) 116 } 117 118 // SafariViewが閉じられた時によばれるdelegateメソッド 119 func safariViewControllerDidFinish(_ controller: SFSafariViewController) { 120 // SafariViewを閉じる 121 dismiss(animated: true, completion: nil) 122 }

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

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

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

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

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

t_obara

2019/02/21 09:25

catchする際に、catch let error { print(error); } として、エラー原因を表示してみましょう。また、参考にしたものがあるのであれば、それを提示してください。参考にしたものと違う部分があるはずなので、その違いを確認してみましょう。
FKM

2019/02/21 10:23 編集

Swiftは知らないので他の有識者に委ねるところですが、PHPを使ったjson取得なら、 グーグルのConsole > ネットワークでjsonの出力データ確認ができます。 そこを確認したら、どんな値が吐き出されているか確認するといいでしょう。 きちんとjson形式のフォーマットになっていなかったら、エラーを吐くはずです。
Tajiko

2019/02/21 09:45

ありがとうございます。 早速catch let error { print(error); }としてみましたら、下記エラーが出ました。 typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)) 構造体の中のl keyword が String型 ではおかしいということでしょうか。 また、参考にしたものは「たった2日でマスターできるiPhoneアプリ開発集中講座 Xcode 10 Swift 4.2対応」という書籍です。質問に追加いたします。
Tajiko

2019/02/21 10:12

FKMさま ありがとうございます。 ご指摘の通り確認しましたところ、JSONの形式は正しいようでした。
FKM

2019/02/21 10:31 編集

あと、PHPで少し変な文章があったので修正しました。そして自分も勘違いがあったんですが catch(PDO::Exception $e) は名の通り、PDOの接続エラーしか返してくれません。ですので、 json_encode()の不備を調べるためには二重にtry-catchが必要ですね try{ $data = json_encode($rows, JSON_UNESCAPED_UNICODE); }catch( Exception $err ){ $data = $err -> getMessage(); } echo $data; //return $json; いらない
guest

回答1

0

ベストアンサー

そのコードだと

JSONは、

json

1{ 2 "item": [{ 3 "latitude": 35.720825, 4 "longitude": 139.927428, 5 "keyword": "JR本八幡駅改札付近北口方面" 6 }, { 7 "latitude": 35.7211, 8 "longitude": 139.927267, 9 "keyword": "都営新宿線本八幡駅 地下1階 駅長事務室の左右" 10 }] 11}

になります。

そのJSONだと

コードは、

swift

1//let json = try decoder.decode(ResultJson.self, from: data!) 2let json = try decoder.decode([Coinlocker].self, from: data!)

になります。

オマケ

エラー表示しましょう。キッカケくらいは掴めるかも知れません。

swift

1//print("エラーがでました") 2print(error)
余談

ライブハウスだからCoin Rockerとか...

投稿2019/02/21 09:38

fuzzball

総合スコア16731

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

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

Tajiko

2019/02/21 10:21

fuzzballさま ありがとうございます。 ご指摘の通りコードを修正しました所、無事にパース、格納でき、デバッグエリアに格納されたデータを表示することが出来ました。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問