APIから取得したJsonをParse

以前ハッカソンですごく苦しんだJsonのParseを克服したのでメモ

APIからデータを取得する

まずAPIからデータを取得します。今回はOpenWetherMapからデータを取得します。

ハッカソンの時はjsonのParseではなくこの取得の部分ですごく苦しみました。
できるのが当然であんまり詳しく解説しているサイトが見つけられませんでした…

APIへのアクセスコード

let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=Hachioji&appid=個人の取得したキー")!
let request = URLRequest(url: url)
var task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    guard let data = data else{return}
    do{
        let jData = try JSONDecoder().decode(Root.self, from: data)
        print(jData.coord.lat)
    } catch let e {
        print(e)
    }
}
task.resume()

どかんと乗っけられたこのコードをそのまま理解できれば良いのですが、私は理解できませんでした。

まず、URLを指定して、APIにリクエストする定数を作っておきます。

let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=Hachioji&appid=個人の取得したキー")!
        let request = URLRequest(url: url)

クロージャ部分をわかりやすく分解して解説します。 

taskはクロージャで内部でAPIとの処理を行います。引数で先ほど指定したURLの定数を渡しと、データ・レスポンス・エラーが帰ってきます。

データがない場合はガード節で弾いています。

var task = URLSession.shared.dataTask(with: request) { (data, response, error) in
   guard let data = data else{return}
}

あとはデータを取得しているdo-catchの中です。
なぜdo-catchがついているかなのですが、.decodeメソッドにthrousがくっついているからです。詳しくは下記の記事がわかりやすいです
猿がついに理解できたSwiftのthrow・do・try・catchの意味

JSONDecoderのdecode関数を使ってデータを取得しているわけですが、JSONSerializationなどの関数でもデータを保持できるようでこの辺がつまりました。

英語弱いのではじめdecodeがよくわかってませんでしたが、リファレンスを読むと、Jsonオブジェクトからデータに変換するメソッドだということがわかりまた。

引数には、Jsonデータを格納するための構造体と取得したデータを渡してあげます。構造体は下の方で解説します。

do{
    let jData = try JSONDecoder().decode(Root.self, from: data)
    print(jData.coord.lat)
} catch let e {
    print(e)
}

これで、取れるだろうと思ってたかをくくっていたのですが、何度ビルドしても取得できずにハマっていました。

公式リファレンスを読んでいると、初期状態はサスペンドモード(省エネモード?)で動いていないので、動かす時はresumeを呼べと書いてありました。

なので、最後にresumeを呼び出して実行してあげています。

task.resume()

CodableでJsonのパース

Codableについては検索すればたくさん情報が出てくるので詳しく説明をする必要はないと思いますが、構造体にCodableプロパティを準拠させることでJsonを打ち込めます。

OpenWetherMapのURLをブラウザで検索すると画像のようなJsonが表示されます。

OpenWetherMap

OpenWetherMapのデータの詳細はここから

これだと階層がよくわからないので、Jsonの構造を綺麗にしてくれるフォーマッターをかけてあげるとこうなります。

{
	"coord": {
		"lon": 139.32,
		"lat": 35.66
	},
	"weather": [{
		"id": 803,
		"main": "Clouds",
		"description": "broken clouds",
		"icon": "04n"
	}],
	"base": "stations",
	"main": {
		"temp": 291.32,
		"pressure": 1019,
		"humidity": 88,
		"temp_min": 290.15,
		"temp_max": 292.15
	},
	"visibility": 16093,
	"wind": {
		"speed": 1.16,
		"deg": 326.002
	},
	"clouds": {
		"all": 75
	},
	"dt": 1539608160,
	"sys": {
		"type": 1,
		"id": 7622,
		"message": 0.0069,
		"country": "JP",
		"sunrise": 1539550150,
		"sunset": 1539590833
	},
	"id": 1863440,
	"name": "Hachioji",
	"cod": 200
}

あとは階層ごとにCodableを準拠した構造体をつくっていくだけです。

実際に値を取得する変数名がkeyと異なっていたり、型が違っているとえらーになってしまうので注意が必要です。

出来上がった構造体がこちらのコードになります。大元の一番大きな構造体に好きな名前をつけてあとは中にあるデータの名前と型を正確に作っていく簡単なお仕事です!

struct Root: Codable{
    var coord: Coord
    var weather: [Weather]
    var base: String
    var main: Main
    var visibility: Int
    var wind: Wind
    var clouds: Clouds
    var dt: Int
    var sys: Sys
    var id: Int
    var name: String
    var cod: Int
    
}
struct Coord: Codable {
    var lon:Double
    var lat:Double
}
struct Weather: Codable {
    var id:Int
    var main:String
    var description:String
    var icon:String
}
struct Main: Codable{
    var temp: Double
    var pressure: Int
    var humidity: Int
    var temp_min: Double
    var temp_max: Double
}
struct Wind: Codable {
    var speed: Double
    var deg: Double
}
struct Clouds: Codable {
    var all: Int
}
struct Sys: Codable {
    var type: Int
    var id: Int
    var message: Double
    var sunrise: Int
    var sunset: Int
}

必要ないとは思いますが一応プロジェクトをGithabに乗っけておきました。
よければ動かしてデータが取得できていることを確認してみてください

ソースコード

https://github.com/harumidiv/ParseJson