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

この記事からわかること
- SwiftでAPI(JSON)を構造体や辞書型へ変換する方法
- JSONSerializationやURLSessionのdataTaskメソッドの使用方法
- 暦APIの導入方法
- 天気予報API(livedoor天気互換)方法
index
[open]
\ アプリをリリースしました /
Swiftで開発しているアプリケーションにAPIを導入して外部からデータを参照する方法をまとめていきます。
APIとは?
そもそもAPIとは「Application Programming Interface」の略称でアプリやプログラム、Webサービス同士を繋ぐインターフェースのことを指します。
サービス同士を繋ぐ形で提供されているものはAPIと呼ばれますが今回はWeb上からJSON形式でデータを受け取り、アプリ内で参照するために使用するようなAPIを使用していきます。
今回使用するのは以下2つのAPIです。
- 暦API:日付情報を提供
- 天気予報API:天気予報を提供
その前にAPIを使用してデータを取得するために必要となる2つのポイントを見ておきます。
- URLSession.shared.dataTask:HTTPリクエストを送信
- JSONSerialization:JSONデータを操作するクラス
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形式のデータを操作する際に使用するクラスです。使用するにはFoundation
をimport
しておく必要があります。
import Foundation
このクラスを使用することでJSONと辞書型の相互変換を行うことができます。JSONデータへと変換するにはjsonObject
メソッドを使用します。
let json = JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
変換されて返ってくるのはAny
型のデータなので任意の方にキャストしておくと便利です。
またライブラリを使用するともっと簡単にJSONを操作することができるようになります。
暦APIの導入方法
まずは日付情報を取得できる「暦API」をSwiftアプリに組み込んで行きます。
暦APIではパラメータによって取得するデータを指定する必要があるためphpを介してJSONデータを取得し表示させるようにしました。少しイレギュラーな実装方法になっています。(最善策があれば教えてください)
実装の流れ
- phpでパラメータを指定しAPIデータを取得→別URLへ出力
- HTTPリクエスト送信→データ(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アプリに組み込んで行きます。
- 公式HP:https://weather.tsukumijima.net/
- API URL:https://weather.tsukumijima.net/api/forecast/city/130010
こちらはクエスト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>
で配列型にキャストする必要があります。
またdescription
のbodyText
の中に\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上に公開しているので興味があれば覗いてみてください。
GitHub:Kiroku-Calendarまだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。