ヤフーショッピングapi json
{
"ResultSet": {
"0": {
"Result": {
"0": {
"Name": "swift ブレーキパッド ≪type-SP (リアシュー)≫ 【bB [NCP31/NCP35(4WD)] 1500 ’00.1〜04.10】",
"Description": "swift スウィフト スイフト ブレーキ ブレーキシュー シュー リヤシュー スーパースポーツ typeSP",
"Headline": "スウィフトSuperSports リア用 左右セット",
"Url": "https://store.shopping.yahoo.co.jp/auto-craft/swift-sp-r00059.html",
"ReleaseDate": "",
"Availability": "instock",
"Code": "auto-craft_swift-sp-r00059",
"Condition": "new",
"Image": {
"Id": "",
"Small": "https://s.yimg.jp/images/sh/noimage/76x76.gif",
"Medium": "https://s.yimg.jp/images/sh/noimage/146x146.gif"
},
"Review": {
"Rate": "0.00",
"Count": "0",
"Url": "https://shopping.yahoo.co.jp/review/item/list?store_id=auto-craft&page_key=swift-sp-r00059"
},
"Affiliate": {
"Rate": "1.0"
},
"Price": {
"_attributes": {
"currency": "JPY"
},
"_value": "11880"
},
"PremiumPrice": "",
"PriceLabel": {
"_attributes": {
"taxIncluded": "true"
},
"FixedPrice": "",
"DefaultPrice": "11880",
"SalePrice": "",
"PremiumPriceStatus": "0",
"PremiumPrice": "11880",
"PremiumDiscountType": "",
"PremiumDiscountRate": "",
"PeriodStart": "",
"PeriodEnd": ""
},
"Point": {
"Amount": "118",
"Times": "1",
"PremiumAmount": "118",
"PremiumTimes": "1"
},
"Shipping": {
"Code": "1",
"Name": "設定無し"
},
"Category": {
"Current": {
"Id": "43160",
"Name": "ブレーキシュー"
}
},
"CategoryIdPath": {
"0": {
"Id": "1"
},
"1": {
"Id": "2514"
},
"2": {
"Id": "41234"
},
"3": {
"Id": "43152"
},
"4": {
"Id": "43160"
},
"_container": "Category"
},
"Brands": {
"Name": "",
"Path": {
"0": {
"Id": ""
},
"_container": "Brand"
}
},
"JanCode": "4571498373717",
"Model": "sp-VS2342",
"IsbnCode": "",
"Store": {
"Id": "auto-craft",
"Name": "オートクラフト",
"Url": "https://store.shopping.yahoo.co.jp/auto-craft/",
"Payment": {
"0": {
"Code": "1",
"Name": "クレジットカード"
},
"1": {
"Code": "16",
"Name": "Yahoo!ウォレットに登録しているクレジットカード"
},
"2": {
"Code": "2",
"Name": "銀行振込"
},
"3": {
"Code": "8",
"Name": "郵便振替"
},
"4": {
"Code": "2048",
"Name": "Yahoo!マネー/預金払い"
},
"_container": "Method"
},
"IsBestStore": "false",
"Ratings": {
"Rate": "4.5",
"Count": "6488",
"Total": "29369",
"DetailRate": "4.5"
},
"Image": {
"Id": "auto-craft_1",
"Medium": "https://item-shopping.c.yimg.jp/s/h/auto-craft_1"
}
},
"IsAdult": "0",
"Deliveryinfo": {
"Area": "",
"Deadline": "",
"Day": ""
},
"_attributes": {
"index": "4"
}
},
"19": {
"Name": "swift ブレーキパッド ≪type-SP (リアシュー)≫ 【ワゴンR [MH21S] 660 ’03.9〜08.9】 型式指定 12751",
"Description": "swift スウィフト スイフト ブレーキ ブレーキシュー シュー リヤシュー スーパースポーツ typeSP",
"Headline": "スウィフトSuperSports リア用 左右セット",
"Url": "https://store.shopping.yahoo.co.jp/auto-craft/swift-sp-r00444.html",
"ReleaseDate": "",
"Availability": "instock",
"Code": "auto-craft_swift-sp-r00444",
"Condition": "new",
"Image": {
"Id": "",
"Small": "https://s.yimg.jp/images/sh/noimage/76x76.gif",
"Medium": "https://s.yimg.jp/images/sh/noimage/146x146.gif"
},
"Review": {
"Rate": "0.00",
"Count": "0",
"Url": "https://shopping.yahoo.co.jp/review/item/list?store_id=auto-craft&page_key=swift-sp-r00444"
},
"Affiliate": {
"Rate": "1.0"
},
"Price": {
"_attributes": {
"currency": "JPY"
},
"_value": "11880"
},
"PremiumPrice": "",
"PriceLabel": {
"_attributes": {
"taxIncluded": "true"
},
"FixedPrice": "",
"DefaultPrice": "11880",
"SalePrice": "",
"PremiumPriceStatus": "0",
"PremiumPrice": "11880",
"PremiumDiscountType": "",
"PremiumDiscountRate": "",
"PeriodStart": "",
"PeriodEnd": ""
},
"Point": {
"Amount": "118",
"Times": "1",
"PremiumAmount": "118",
"PremiumTimes": "1"
},
"Shipping": {
"Code": "1",
"Name": "設定無し"
},
"Category": {
"Current": {
"Id": "43160",
"Name": "ブレーキシュー"
}
},
"CategoryIdPath": {
"0": {
"Id": "1"
},
"1": {
"Id": "2514"
},
"2": {
"Id": "41234"
},
"3": {
"Id": "43152"
},
"4": {
"Id": "43160"
},
"_container": "Category"
},
"Brands": {
"Name": "",
"Path": {
"0": {
"Id": ""
},
"_container": "Brand"
}
},
"JanCode": "4571498374219",
"Model": "sp-VS9967",
"IsbnCode": "",
"Store": {
"Id": "auto-craft",
"Name": "オートクラフト",
"Url": "https://store.shopping.yahoo.co.jp/auto-craft/",
"Payment": {
"0": {
"Code": "1",
"Name": "クレジットカード"
},
"1": {
"Code": "16",
"Name": "Yahoo!ウォレットに登録しているクレジットカード"
},
"2": {
"Code": "2",
"Name": "銀行振込"
},
"3": {
"Code": "8",
"Name": "郵便振替"
},
"4": {
"Code": "2048",
"Name": "Yahoo!マネー/預金払い"
},
"_container": "Method"
},
"IsBestStore": "false",
"Ratings": {
"Rate": "4.5",
"Count": "6488",
"Total": "29369",
"DetailRate": "4.5"
},
"Image": {
"Id": "auto-craft_1",
"Medium": "https://item-shopping.c.yimg.jp/s/h/auto-craft_1"
}
},
"IsAdult": "0",
"Deliveryinfo": {
"Area": "",
"Deadline": "",
"Day": ""
},
"_attributes": {
"index": "23"
}
},
"Request": {
"Query": "swift"
},
"Modules": "",
"_container": "Hit"
}
},
"totalResultsAvailable": "252014",
"totalResultsReturned": 20,
"firstResultPosition": "4"
}
}
SearchItemTableViewController.swift
import UIKit
class SearchItemTableViewController: UITableViewController,
UISearchBarDelegate {
var itemDataArray = [ItemData]()
var imageCache = NSCache()
var itemAllCount: String = "" // 自分で追加したコード
// APIを利用するためのクライアントID
let appid = "apiのid"
let entryUrl: String =
"https://shopping.yahooapis.jp/ShoppingWebService/V1/json/itemSearch"
// 数字を金額の形式に整形するためのフォーマッター
let priceFormat = NumberFormatter()
override func viewDidLoad() {
super.viewDidLoad()
// 価格のフォーマット指定
priceFormat.numberStyle = .currency
priceFormat.currencyCode = "JPY"
}
// キーボードのsearchボタンがタップされたときに呼び出される
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
// 入力された文字の取り出し
guard let inputText = searchBar.text else {
// 入力文字なし
return
}
// 入力文字数が0文字より多いかどうかチェックする
guard inputText.lengthOfBytes(using: String.Encoding.utf8) > 0 else {
// 0文字より多くはなかった
return
}
// 保持している商品をいったん削除
itemDataArray.removeAll()
// パラメータを指定する
let parameter = ["appid": appid, "query": inputText]
// パラメータをエンコードしたURLを作成する
let requestUrl = createRequestUrl(parameter: parameter)
// APIをリクエストする
request(requestUrl: requestUrl)
// キーボードを閉じる
searchBar.resignFirstResponder()
}
// パラメータのURLエンコード処理
func encodeParameter(key: String, value: String) -> String? {
// 値をエンコードする
guard let escapedValue = value.addingPercentEncoding(
withAllowedCharacters: CharacterSet.urlQueryAllowed) else {
// エンコード失敗
return nil
}
// エンコードした値をkey=valueの形式で返却する
return "\(key)=\(escapedValue)"
}
// URL作成処理
func createRequestUrl(parameter: [String: String]) -> String {
var parameterString = ""
for key in parameter.keys {
// 値の取り出し
guard let value = parameter[key] else {
// 値なし。次のfor文の処理を行う
continue
}
// すでにパラメータが設定されていた場合
if parameterString.lengthOfBytes(using: String.Encoding.utf8) > 0 {
// パラメータ同士のセパレータである&を追加する
parameterString += "&"
}
// 値をエンコードする
guard let encodeValue = encodeParameter(key: key, value: value)
else {
// エンコード失敗。次のfor文の処理を行う
continue
}
// エンコードした値をパラメータとして追加する
parameterString += encodeValue
}
let requestUrl = entryUrl + "?" + parameterString
return requestUrl
}
// リクエストを行う
func request(requestUrl: String) {
// URL生成
guard let url = URL(string: requestUrl) else {
// URL生成失敗
return
}
// リクエスト生成
let request = URLRequest(url: url)
// 商品検索APIをコールして商品検索を行う
let session = URLSession.shared
let task = session.dataTask(with: request) { (data:Data?,
response:URLResponse?, error:Error?) in
// 通信完了後の処理
// エラーチェック
guard error == nil else {
// エラー表示
let alert = UIAlertController(title: "エラー",
message: error?.localizedDescription,
preferredStyle: UIAlertController.Style.alert)
// UIに関する処理はメインスレッド上で行う
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
return
}
// JSONで返却されたデータをパースして格納する
guard let data = data else {
// データなし
return
}
do {
// パース実施
let resultSet =
try JSONDecoder().decode(ItemSearchResultSet.self,
from: data)
// 商品のリストに追加
self.itemDataArray.append(contentsOf:
resultSet.resultSet.firstObject.result.items)
self.itemAllCount = resultSet.resultSet.totalResultsAvailable // 自分で追加したコード
} catch let error {
print("## error: \(error)")
}
// テーブルの描画処理を実施
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
// 通信開始
task.resume()
}
// MARK: - Table view data source
// テーブルのセクション数を取得
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// セクション内の商品数を取得
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return itemDataArray.count
}
// MARK: - Table view data source
// テーブルセルの取得処理
override func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier:
"itemCell", for: indexPath) as? ItemTableViewCell else {
return UITableViewCell()
}
let itemData = itemDataArray[indexPath.row]
//print(itemAllCount)
// 商品のタイトル設定
cell.itemTitleLabel.text = itemData.name
// 商品価格設定処理(日本通貨の形式で設定する)
let number = NSNumber(integerLiteral: Int(itemData.priceInfo.price!)!)
cell.itemPriceLabel.text = priceFormat.string(from: number)
// 商品のURL設定
cell.itemUrl = itemData.url
cell.itemAllCountLabel.text = itemAllCount // 自分で追加したコード itemAllCountが取得できていない
// 画像の設定処理
// すでにセルに設定されている画像と同じかどうかチェックする
// 画像がまだ設定されていない場合に処理を行う
guard let itemImageUrl = itemData.imageInfo.medium else {
// 画像なし商品
return cell
}
// キャッシュの画像を取り出す
if let cacheImage = imageCache.object(forKey: itemImageUrl as
AnyObject) {
// キャッシュ画像の設定
cell.itemImageView.image = cacheImage
return cell
}
// キャッシュの画像がないためダウンロードする
guard let url = URL(string: itemImageUrl) else {
// urlが生成できなかった
return cell
}
let request = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: request) { (data:Data?,
response:URLResponse?, error:Error?) in
guard error == nil else {
// エラーあり
return
}
guard let data = data else {
// データが存在しない
return
}
guard let image = UIImage(data: data) else {
// imageが生成できなかった
return
}
// ダウンロードした画像をキャッシュに登録しておく
self.imageCache.setObject(image, forKey: itemImageUrl as AnyObject)
// 画像はメインスレッド上で設定する
DispatchQueue.main.async {
cell.itemImageView.image = image
}
}
// 画像の読み込み処理開始
task.resume()
return cell
}
// 商品をタップして次の画面に遷移する前の処理
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let cell = sender as? ItemTableViewCell {
if let webViewController =
segue.destination as? WebViewController {
// 商品ページのURLを設定する
webViewController.itemUrl = cell.itemUrl
}
}
}
}
temSearchResultSet.swift
import Foundation // 検索結果全体を格納するクラス class ItemSearchResultSet: Codable { var resultSet: ResultSet private enum CodingKeys: String, CodingKey { case resultSet = "ResultSet" } } // 検索結果セット格納クラス class ResultSet: Codable { var firstObject: FirstObject var totalResultsAvailable: String = "" // 自分で追加したコード private enum CodingKeys: String, CodingKey { case firstObject = "0" } } // 検索結果の先頭を格納するクラス class FirstObject: Codable { var result: Result private enum CodingKeys: String, CodingKey { case result = "Result" } } // 検索結果格納クラス class Result: Codable { var items: [ItemData] = [ItemData]() required init(from decoder: Decoder) throws { // デコードのためのコンテナを取得 let container = try decoder.container(keyedBy: CodingKeys.self) // コンテナ内のキーを取得。キーが文字列であるため、数値の昇順でソートも行う let keys = container.allKeys.sorted { Int($0.rawValue)! < Int($1.rawValue)! } // キーを使用して検索結果を一件ずつ取り出す for key in keys { // 検索結果一件に対するデコード処理 let item = try container.decode(ItemData.self, forKey: key) // デコード処理できたら検索結果の一覧に追加 items.append(item) } } // エンコード処理 func encode(to encoder: Encoder) throws { // レスポンスを解析するだけなので、実装不要 } // Resultクラスが持つ値を取得するためのキー private enum CodingKeys: String, CodingKey { case hit0 = "0" case hit1 = "1" case hit2 = "2" case hit3 = "3" case hit4 = "4" case hit5 = "5" case hit6 = "6" case hit7 = "7" case hit8 = "8" case hit9 = "9" case hit10 = "10" case hit11 = "11" case hit12 = "12" case hit13 = "13" case hit14 = "14" case hit15 = "15" case hit16 = "16" case hit17 = "17" case hit18 = "18" case hit19 = "19" case hit20 = "20" } } // 商品情報格納クラス class ItemData: Codable { // 商品名 var name: String = "" // 商品URL var url: String = "" // 商品画像情報 class ImageInfo: Codable { // Imageクラスが持つ値を取得するためのキー private enum CodingKeys: String, CodingKey { case medium = "Medium" } // 商品画像URL var medium: String? } // 商品画像URL var imageInfo: ImageInfo = ImageInfo() // 価格情報 class PriceInfo: Codable { // Priceクラスが持つ値を取得するためのキー private enum CodingKeys: String, CodingKey { case price = "_value" } // 価格 var price: String? } // 価格 var priceInfo: PriceInfo = PriceInfo() private enum CodingKeys: String, CodingKey { case name = "Name" case url = "Url" case imageInfo = "Image" case priceInfo = "Price" } }