24
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

swiftで配列型のJSONから値を取り出す

Last updated at Posted at 2020-01-03

概要

 この記事は初心者の自分がRESTfulなAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっています。

 前回のDBに格納したデータを返すwebAPIをDjangoとSQLiteで開発するでWebAPIの仕様を変えたので、新しいAPIの仕様に合わせて、swiftでwebAPIを呼び出してjsonデータを表示させる で作成したswiftのコードを改造します。大きな変更点は、レスポンスのjsonオブジェクトが配列になった点です。これにより複数のクーポン情報が取得出来るようになりました。

これまでのjson (例)

{“name”:”beer”,”price”:500}

新しいjson (例)

[
	{“name”:”beer”,”price”:500}
	{“name”:”vodka”,”price”:700}
	{“name”:”scotch”,”price”:800}
]

jsonのデータを取り出してパースする処理を配列型に対応させる必要があります。

参考

環境

Mac OS 10.15
Swift5
Xcode11.1

手順

  • レスポンスの内容をData型からjson形式に変換しコンソールで確認する
  • jsonをAny型にキャストする
  • 配列に格納されているjsonのオブジェクトを一つずつ String型にキャストする

レスポンスのjsonの形式を確認する

 webAPI側はjsonをオブジェクトの配列で返す仕様になっていますが、念のためどの様なjsonが返ってくるか確認します。

まずはブラウザでAPIのURLにアクセスしてjsonの形式を確認します。
check-api-response-by-safari-1200.png

次にターミナルでも確認してみます。ターミナルの場合は curl コマンドで APIのURLにアクセスします。
check-api-response-by-curl-mask.png

curlコマンドを使う方が改行がされていて確認しやすいですね。もっと複雑なJSONの確認をする場合は専用のツールを使うの方法があるそうです。

想定した通り、配列型の下記の様な形式のjsonになっています。

[
  {...}
  {...}
  {...}
]

次にXcodeでData型をjsonに変換しただけのデータをコンソールに出力させてみます。swiftでwebAPIを呼び出してjsonデータを表示させる で作成したコードを使う場合は、ViewController.swiftviewDidLoadの一部を下記の通りコメントアウトや修正します。

  • APIのURLをhttp://127.0.0.1:8000/coupon/に変更
  • JSONSerialization〜の後ろの as! [String: Any]は型違いでエラーになるので削除
  • TableViewとデータをやり取りするためのグルーバル変数に代入する処理をコメントアウト

修正後のviewDidLoadはこちらです。

ViewController.viewDidLoad

    override func viewDidLoad() {
        super.viewDidLoad()


        let url: URL = URL(string: "http://127.0.0.1:8000/coupon/")! // URLの変更
        let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
            do  {
                let couponData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments)
 print(couponData)

/*  ここはコメントアウト                               
                DispatchQueue.main.async() { () -> Void in
                    self.couponBenefit = couponData["coupon_benefits"] as! String
                    self.couponDeadline = couponData["coupon_deadline"] as! String
                    }
*/

                }
            catch {
                print(error)
                }
        })
        task.resume()
    }

この状態で実行してコンソールに出力されるjsonを確認します。すると、下記の様に出力されます。

check-api-response-by-console.png

ブラウザやcurlで表示されるjsonは外側のかっこが[ ]ですが、アプリ上ではかっこが( )になっています。このままでは配列として扱えず、中のデータが取り出せません。そこで、Any型にキャストします。

JSONをAny型にキャストする

JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments)の後ろに as! [Any] を付けるだけです。修正前のコードではas! [String: Any]が付いていたところです。


do  {
     let couponData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as! [Any] //Any型にキャスト
     print(couponData)

			//...中略...

     }
     catch {
         print(error)
         }

すると、コンソールに出力されたjsonの外側のかっこが( )から[ ]に変わりました。これで配列として扱えるようになりました。
check-api-resopnse-after-cast.png

配列に格納されているjsonのオブジェクトを一つずつ String型にキャストする

 jsonクラスのmapという関数を使い、jsonの配列のオブジェクトに対して[String: Any]型にキャストしたものを新しい配列couponDataに格納するようにします。

JSONSErializationの処理の下に以下の処理を追加します。


let couponData = couponDataArray.map { (couponData) -> [String: Any] in
    return couponData as! [String: Any]
}

ここまでの処理で個々のjsonオブジェクトの情報を取り出せるようになりました。情報を取り出す際、変数couponDataはjsonオブジェクトと、オブジェクトに格納されているモデルフィールドの二重配列となっているので、
個々のデータは、couponData[オブジェクトの番号(0~)][“モデルフィールド名(jsonのキーと同じ)”]で指定できます。

但しそのままだと[String: Any]型なので、取り出すモデルフィールドの型に合わせてキャストします。文字型の場合はas! Stringでキャストします。下記のようなコードでデータを取り出していきます。

couponData[オブジェクトの番号(0~)][“JSONのキー名”] as! [モデルフィールドの型]

試しに以下のコードを追加し、クーポン2つ分のクーポン特典をコンソールに出力させてみます。


print(couponData[0]["coupon_benefits"] as! String)
print(couponData[1]["coupon_benefits"] as! String)

実行してコンソールを見ると、クーポン2つ分のクーポン特典が出力されているので処理が成功しています。
out-put-data-by-2objects.png

以上でオブジェクトの配列形式のjsonから個々の情報を取り出せるようになりました。

複数のクーポン情報をTableViewで表示するように改造する。

 複数のクーポンの情報をレスポンス出来るようになったので、TableViewに複数のクーポンの情報を表示できるようにします。

まず、TableViewにデータを渡す方法を変え、jsonオブジェクトの配列を[String: Any]型にパースした 変数couponDataをそのままTableViewに渡すようにします。そのためメンバー変数の定義部分を下記のように修正します。

  • メンバー変数の型を[String: Any]型にし、couponDataをそのまま格納出来るようにする
  • オブジェクトの項目毎のメンバ変数は不要なので削除

    var coupons: [[String: Any]] = [] { //パースした[String: Any]型のクーポンデータを格納するメンバ変数
        didSet{
            tableView.reloadData()
        }
    }

次にテーブルの行数を渡している部分を改造します。配列のcountメソッドを使ってデータを格納するメンバ変数(coupons)の配列の行数を渡すようにします。


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.coupons.count
    }

次にテーブルに表示するデータを渡している部分を改造します。

辞書型変数のcouponを定義し、couponsの配列の1行(jsonの{...}で囲まれたオブジェクト)ごとにcouponに格納します。そして辞書型のcouponからモデルフィールドの名前(jsonのキー)でデータを取り出し、データをString型にキャストしてセルに渡します。


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell")
        //辞書型変数のcouponを定義
        let coupon = self.coupons[indexPath.row]

        //モデルフィールドの名前でデータを取り出し、String型にキャストしてセルに渡す
        cell.textLabel?.text = (coupon["coupon_benefits"] as! String)
        cell.detailTextLabel?.text = "有効期限:" + (coupon["coupon_deadline"] as! String)

        return cell
    }

最後にviewDidLoadでjsonをパースしてメンバ変数に格納する部分を改造します。

String型の変数にjsonオブジェクト内のデータを直接格納していましたが、[String: Any]型のままTableViewに渡す仕様となったため、jsonをパースした[String: Any]型のデータをそのままメンバ変数couponsに渡すようにします。


      DispatchQueue.main.async() { () -> Void in
          self.coupons = couponData
      }

アプリを起動すると、先にjsonで確認した3件つのクーポンがTableViewで表示されるようになりました。
test-ami-coupon-02.png

修正後のViewControllerクラスは下記の通りです。

ViewController.swift

import UIKit

class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
    @IBOutlet weak var tableView: UITableView!
    
    var coupons: [[String: Any]] = [] { //パースした[String: Any]型のクーポンデータを格納するメンバ変数
        didSet{
            tableView.reloadData()
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let url: URL = URL(string: "http://127.0.0.1:8000/coupon/")!
        let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
            do  {
                let couponDataArray = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as! [Any]
                let couponData = couponDataArray.map { (couponData) -> [String: Any] in
                    return couponData as! [String: Any]
                }
                
                DispatchQueue.main.async() { () -> Void in
                    self.coupons = couponData
                    }
                }
            catch {
                print(error)
                }
        })
        task.resume()
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.coupons.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell")
        //辞書型変数のcouponを定義
        let coupon = self.coupons[indexPath.row]

        //モデルフィールドの名前でデータを取り出し、String型にキャストしてセルに渡す
        cell.textLabel?.text = (coupon["coupon_benefits"] as! String)
        cell.detailTextLabel?.text = "有効期限:" + (coupon["coupon_deadline"] as! String)

        return cell
    }
    
}

以上です。

次回は TableViewを改造して、取得したクーポン情報をアプリ画面に表示出来るようにします。

24
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?