【Swift】API(JSON形式)を構造体や辞書型へ変換!JSONSerialization

この記事からわかること

  • SwiftAPI(JSON)を構造体辞書型変換する方法
  • JSONSerializationURLSessiondataTaskメソッド使用方法
  • 暦API導入方法
  • 天気予報API(livedoor天気互換)方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

Swiftで開発しているアプリケーションにAPIを導入して外部からデータを参照する方法をまとめていきます。

APIとは?

そもそもAPIとは「Application Programming Interface」の略称でアプリやプログラム、Webサービス同士を繋ぐインターフェースのことを指します。

サービス同士を繋ぐ形で提供されているものはAPIと呼ばれますが今回はWeb上からJSON形式でデータを受け取り、アプリ内で参照するために使用するようなAPIを使用していきます。

今回使用するのは以下2つのAPIです。

その前にAPIを使用してデータを取得するために必要となる2つのポイントを見ておきます。

URLSession.shared.dataTask

公式リファレンス:URLSession.shared.dataTask

SwiftからHTTPリクエストを送信するにはURLSession.shared.dataTaskを使います。

func dataTask(
    with request: URLRequest,
    completionHandler: @escaping  @Sendable (Data?, URLResponse?, Error?) -> Void
) -> URLSessionDataTask

おすすめ記事:【Swift】URLSessionクラスとは?URLRequestとHTTP通信!

引数に対象のURLを渡すとその結果がcompletionHandlerで受け取れます。1つ目にはサーバーから返されたデータが、2つ目にはHTTPヘッダーやステータスコードなどが、3つ目にはリクエスト失敗時の理由などを受け取れます。

let urlString = "https://appdev-room.com/"
// 有効なURLかをチェック
if validationUrl(urlString: urlString) == false {
    return
}
guard let url = URL(string: urlString) else {
    return
}
// リクエストを構築
let request = URLRequest(url: url)

// URLにアクセスしてレスポンスを取得する
URLSession.shared.dataTask(with: request) { data, response, error in
// HTTPリクエストを送信して取得したデータ操作
}.resume() 

URLリクエストを構築する前にURLの有効性をチェックしておきます。ここでは独自のメソッドvalidationUrlを定義しています。詳細は下記記事を参考にしてください。

おすすめ記事:【SwiftUI】入力されたURLの有効性を識別する方法!

JSONSerialization

JSONSerializationクラスはSwiftでJSON形式のデータを操作する際に使用するクラスです。使用するにはFoundationimportしておく必要があります。

import Foundation

このクラスを使用することでJSONと辞書型の相互変換を行うことができます。JSONデータへと変換するにはjsonObjectメソッドを使用します。

let json = JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]

変換されて返ってくるのはAny型のデータなので任意の方にキャストしておくと便利です。

またライブラリを使用するともっと簡単にJSONを操作することができるようになります。

暦APIの導入方法

まずは日付情報を取得できる「暦API」をSwiftアプリに組み込んで行きます。

暦APIではパラメータによって取得するデータを指定する必要があるためphpを介してJSONデータを取得し表示させるようにしました。少しイレギュラーな実装方法になっています。(最善策があれば教えてください)

実装の流れ

取得できるJSONデータ

{
	"datelist":{
		"2015-12-01":{
			"week":"火",
			"inreki":"師走",
			"zyusi":"辛",
			"zyunisi":"亥",
			"eto":"未",
			"gengo":"平成",
			"wareki":27,
			"sekki":"",
			"kyurekiy":2015,
			"kyurekim":10,
			"kyurekid":20,
			"rokuyou":"大安",
			"holiday":""
		},
		"2015-12-02":{
			"week":"水",
			"inreki":"師走",
			"zyusi":"壬",
			"zyunisi":"子",
			"eto":"未",
			"gengo":"平成",
			"wareki":27,
			"sekki":"",
			"kyurekiy":2015,
			"kyurekim":10,
			"kyurekid":21,
			"rokuyou":"赤口",
			"holiday":""
		}
	}
}

phpからパラメータを指定しAPIデータを取得

ここはSwiftではなくphpでの記述になります。私の場合はこのサイトを運営しているので「https://appdev-room.com/rokuyou」にアクセスした際に必要なパラメータを渡し済みの暦APIデータを出力するようにしています。

<?php

$api = 'https://koyomi.zingsystem.com/api/';
$now = new DateTime(); // 引数なし
$month =  $now->format('m'); // 結果:現在の日付/時刻
$month = $month - 6;
$param = array(
  'mode' => "m", 'cnt' => "12", 'targetyyyy' => date("Y"), 'targetmm' => $month, 'targetdd' => date("d")
);
$ch = curl_init($api);

curl_setopt($ch, CURLOPT_URL, $api);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);


curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($param));
echo curl_getinfo($ch, CURLINFO_HEADER_OUT);
$result = curl_exec($ch);
curl_close($ch);

echo $result;

これで先ほどのURLにアクセスすると当日を中心にした1年間の暦情報を持ったJSONデータを取得できます。これを今度はSwift側で操作していきます。

PHPやcURLの使い方に関しては下記記事を参考にしてください。

HTTPリクエスト送信→データ(JSON文字列)を取得

Swift側ではデータを取得するためのクラス(fetchDateInfoAPI)を用意しておきます。HTTPリクエストを送信してデータを取得後にcompletionHandlerを使用して辞書型に変換したデータにアクセスできるメソッドを定義していきます。

import UIKit

class fetchDateInfoAPI: NSObject {

    func validationUrl (urlString: String) -> Bool {
        if let nsurl = NSURL(string: urlString) {
            return UIApplication.shared.canOpenURL(nsurl as URL)
        }
        return false
    }
    
    func getDateInfoFromKOYOKMIAPI(completion: @escaping ([String:Any]) -> Void) {
      //  HTTPリクエストを送信してデータを取得する処理
    }
}

まずはAPIを取得するためのリクエストURLからJSONデータを取得する処置を記述します。URLが有効かどうかを識別し問題なければリクエストを構築してdataTaskメソッドを呼び出します。

let urlString = "https://appdev-room.com/rokuyou"
// 有効なURLかをチェック
if validationUrl(urlString: urlString) == false {
    return
}
guard let url = URL(string: urlString) else {
    return
}
// リクエストを構築
let request = URLRequest(url: url)

// URLにアクセスしてレスポンスを取得する
URLSession.shared.dataTask(with: request) { data, response, error in
}.resume() 

辞書型に変換

Swiftで扱えるようにJSONデータを変換するにはJSONSerializationでできました。返り値はAny型だったので辞書型に変換するためas? [String: Any]を後ろにつけてキャストしておきます。

これで正常に変換できればdicの中に辞書型で保持されたデータが入ります。.keysメソッドなどでキーとなっている文字列を見てみるとわかりやすいです。

暦APIではキーdatelistの中に日付をキーとして日付情報を保持しているのでdic!["datelist"]と決め打ちして中身を取得しています。

if let data = data {
    do {
        let dic = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
        let dayInfoAPI = dic!["datelist"] as? [String: Any] // キーを指定して中身を取り出す
        print(dayInfoAPI?.keys) //  Optional(Dictionary.Keys(["2022-10-21", "2022-07-19"......
        completion(dayInfoAPI!)
    } catch {
        print(error.localizedDescription)
    }
} else {
    // データが取得できなかった場合の処理
    print(error?.localizedDescription ?? "不明なエラー")
}

あとはこのメソッドを任意の場所で呼び出してプロパティなどに格納しておけば自由にアクセスすることができるようになります。

呼び出し使用例

func loadDateAPI() {
  let api = fetchDateInfoAPI()
  api.getDateInfoFromKOYOKMIAPI { data in
    DispatchQueue.main.async {
        self.dateInfoAPI = data
    }
  }
}

全体のコード


import UIKit

class fetchDateInfoAPI: NSObject {
  
  func validationUrl (urlString: String) -> Bool {
      if let nsurl = NSURL(string: urlString) {
          return UIApplication.shared.canOpenURL(nsurl as URL)
      }
      return false
  }
  
  func getDateInfoFromKOYOKMIAPI(completion: @escaping ([String:Any]) -> Void) {
          
      // MARK: - https://koyomi.zingsystem.com/api/

      let urlString = "https://appdev-room.com/rokuyou"
      // 有効なURLかをチェック
      if validationUrl(urlString: urlString) == false {
          return
      }
      guard let url = URL(string: urlString) else {
          return
      }
      // リクエストを構築
      let request = URLRequest(url: url)
      
      // URLにアクセスしてレスポンスを取得する
      URLSession.shared.dataTask(with: request) { data, response, error in
      
          if let data = data {
              do {
                  let dic = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                  let dayInfoAPI = dic!["datelist"] as? [String: Any]
                  completion(dayInfoAPI!)
              } catch {
                print(error.localizedDescription)
              }
          } else {
              // データが取得できなかった場合の処理
              print(error?.localizedDescription ?? "不明なエラー")
          }
      }.resume()     
  }
}

天気予報APIの導入方法

天気予報API」をSwiftアプリに組み込んで行きます。

こちらはクエストURL末尾に任意のエリアコードを渡すことでその地域の天気予報を3日分JSONデータで取得することができます。なのでPHPは介さずにアクセスしますが、取得できるJSONはやや複雑な構造をしています。

取得できるJSONデータ

{
    "publicTime": "2022-11-08T17:00:00+09:00",
    "publicTimeFormatted": "2022/11/08 17:00:00",
    "publishingOffice": "気象庁",
    "title": "東京都 東京 の天気",
    "link": "https://www.jma.go.jp/bosai/forecast/#area_type=offices&area_code=130000",
    "description": {
        "publicTime": "2022-11-08T16:39:00+09:00",
        "publicTimeFormatted": "2022/11/08 16:39:00",
        "headlineText": "",
        "bodyText": " 本州付近は〜さい。"
    },
    "forecasts": [
        {
            "date": "2022-11-08",
            "dateLabel": "今日",
            "telop": "晴時々曇",
            "detail": {
                "weather": "晴れ 夜のはじめ頃 くもり",
                "wind": "北の風",
                "wave": "0.5メートル"
            },
            "temperature": {
                "min": {
                    "celsius": null,
                    "fahrenheit": null
                },
                "max": {
                    "celsius": null,
                    "fahrenheit": null
                }
            },
            "chanceOfRain": {
                "T00_06": "--%",
                "T06_12": "--%",
                "T12_18": "--%",
                "T18_24": "10%"
            },
            "image": {
                "title": "晴時々曇",
                "url": "https://www.jma.go.jp/bosai/forecast/img/101.svg",
                "width": 80,
                "height": 60
            }
        },
        {
            "date": "2022-11-09",
            "dateLabel": "明日",
            "telop": "晴時々曇",
            "detail": {
                "weather": "晴れ 朝晩 くもり",
                "wind": "北の風 後 南東の風",
                "wave": "0.5メートル"
            },
            "temperature": {
                "min": {
                    "celsius": "10",
                    "fahrenheit": "50"
                },
                "max": {
                    "celsius": "18",
                    "fahrenheit": "64.4"
                }
            },
            "chanceOfRain": {
                "T00_06": "0%",
                "T06_12": "0%",
                "T12_18": "0%",
                "T18_24": "10%"
            },
            "image": {
                "title": "晴時々曇",
                "url": "https://www.jma.go.jp/bosai/forecast/img/101.svg",
                "width": 80,
                "height": 60
            }
        },
        {
            "date": "2022-11-10",
            "dateLabel": "明後日",
            "telop": "晴時々曇",
            "detail": {
                "weather": "晴れ 時々 くもり",
                "wind": "南の風",
                "wave": "0.5メートル"
            },
            "temperature": {
                "min": {
                    "celsius": "11",
                    "fahrenheit": "51.8"
                },
                "max": {
                    "celsius": "22",
                    "fahrenheit": "71.6"
                }
            },
            "chanceOfRain": {
                "T00_06": "10%",
                "T06_12": "10%",
                "T12_18": "10%",
                "T18_24": "10%"
            },
            "image": {
                "title": "晴時々曇",
                "url": "https://www.jma.go.jp/bosai/forecast/img/101.svg",
                "width": 80,
                "height": 60
            }
        }
    ],
    "location": {
        "area": "関東",
        "prefecture": "東京都",
        "district": "東京地方",
        "city": "東京"
    },
    "copyright": {
        "title": "(C) 天気予報 API(livedoor 天気互換)",
        "link": "https://weather.tsukumijima.net/",
        "image": {
            "title": "天気予報 API(livedoor 天気互換)",
            "link": "https://weather.tsukumijima.net/",
            "url": "https://weather.tsukumijima.net/logo.png",
            "width": 120,
            "height": 120
        },
        "provider": [
            {
                "link": "https://www.jma.go.jp/jma/",
                "name": "気象庁 Japan Meteorological Agency",
                "note": "気象庁 HP にて配信されている天気予報を JSON データへ編集しています。"
            }
        ]
    }
}

実装のコード

今回取得したい情報は天気予報部分(forecasts)の中です。辞書型でforecastsまでアクセスしたらその中は配列形式で天気情報を保持しているのでas? Array<Any>配列型にキャストする必要があります。

またdescriptionbodyTextの中に\nを含んでいたのが原因でJSONデータを辞書型に変換できなかったので一度文字列に変換後、改行文字を置換し再度データ型にしてから辞書型にキャストしています。

class fetchWeatherAPI: NSObject {
    
  func getWeatherFromTENKIYOHOUAPI(completion: @escaping (Array<Any>?) -> Void) {

    let urlString = "https://weather.tsukumijima.net/api/forecast/city/130010"
    // 有効なURLかをチェック
    if validationUrl(urlString: urlString) == false {
        return
    }
    guard let url = URL(string: urlString) else {
        return
    }
    // リクエストを構築
    let request = URLRequest(url: url)
      
    // URLにアクセス
    URLSession.shared.dataTask(with: request) { data, response, error in
        
      if var data = data {
          do {
              // MARK: - 文字列に変換→改行コードを置換→データ型に変換→辞書型に変換
              let str = String(data: data, encoding: .utf8)
              let json = str?.replacingOccurrences(of: "\n", with: "") ?? ""
              data = json.data(using: .utf8)!
              let dic = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
              let weatherAPI = dic!["forecasts"]  as? Array<Any>
              completion(weatherAPI ?? nil)
          } catch {
            print(error.localizedDescription)
          }
      } 
    }.resume()
  }
}

今回のAPIを実際に導入したiOSアプリを公開しています。ソースコードもGitHub上に公開しているので興味があれば覗いてみてください。

記録カレンダー

記録カレンダー

無料posted withアプリーチ

GitHub:Kiroku-Calendar

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index