先日はじめてJSONデータをちゃんとパースする書き方をしたので、まとめます。
構造体を使わずに無理やりキャストする例
Codableプロトコルを使わなくても、やる方法はあります。
相当な力技なんですが、二ヶ月前くらいに処理したときはこれでやりました。
Swift3でJSONパースを行う
とかを参考にして、無理やりキャストしたのが、下記です。
func getNumberOfPhotos(url: URL) -> Int {
var request = URLRequest(url: url)
var returnnumberOfPhotos = 0
let semaphore = DispatchSemaphore(value: 0)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
do {
let dictinaryForJSON = try JSONSerialization.jsonObject(with: data, options: []) as! Dictionary<String, Any>
let dictionaryForJSONBydata = dictinaryForJSON["data"]! as! Dictionary<String, Any>
let dictionaryForJSONBydataBycounts = dictionaryForJSONBydata["counts"]! as! Dictionary<String, Int>
returnnumberOfPhotos = dictionaryForJSONBydataBycounts["media"]!
} catch let e {
print(e)
}
}
task.resume()
return returnnumberOfPhotos
}
うん、汚いですね。
可読性めちゃくちゃ悪いな……とは当時から思ってました。
InstagramのAPIが返してくる、下記のJSONデータをparseしたかったのが目的でした。
{
"data": {
"id": "1574083",
"username": "snoopdogg",
"full_name": "Snoop Dogg",
"profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
"bio": "This is my bio",
"website": "http://snoopdogg.com",
"is_business": false,
"counts": {
"media": 1320,
"follows": 420,
"followed_by": 3410
}
}
}
初心者過ぎて構造体のマッピングの方法がわからずに、こんなやり方をしましたが、この方法をとるメリットはないですね。
構造体にマッピングする例
APIと通信する際は、アプリ側でも受け取る構造体を定義しましょう。
Githubのユーザー検索APIだと、
{
"total_count": 12,
"incomplete_results": false,
"items": [
{
"login": "mojombo",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://secure.gravatar.com/avatar/25c7c18223fb42a4c6ae1c8db6f50f9b?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png",
"gravatar_id": "",
"url": "https://api.github.com/users/mojombo",
"html_url": "https://github.com/mojombo",
"followers_url": "https://api.github.com/users/mojombo/followers",
"subscriptions_url": "https://api.github.com/users/mojombo/subscriptions",
"organizations_url": "https://api.github.com/users/mojombo/orgs",
"repos_url": "https://api.github.com/users/mojombo/repos",
"received_events_url": "https://api.github.com/users/mojombo/received_events",
"type": "User",
"score": 105.47857
}
]
}
こんなJSONオブジェクトを返してきます。
これをSwiftで書いてやると、
struct User: Codable {
let total_count: Int
let incomplete_results: Bool
let items: [Item]
struct Item: Codable {
let login: String
let id: Int
let node_id: String
let avatar_url: URL
let gravatar_id: String?
let url: URL
let html_url: URL
let followers_url: URL
let subscriptions_url: URL
let organizations_url: URL
let repos_url: URL
let received_events_url: URL
let type: String
let score: Double
}
}
こんな感じになります。
Codableプロトコルに準拠させるのがポイントです。
もしもiOSアプリで必要な情報が限られて、絞りたいのであれば、
構造体の中でその変数だけ用意してやればOKです。
ただし、構造体で定義した変数がAPIから返されたデータの中に入っていないと、parseに失敗します。
func searchGithubUser(query: String) -> User {
let url = URL(string: "https://api.github.com/search/users?q=" + query)!
var request = URLRequest(url: url)
var rowData = Data()
let semaphore = DispatchSemaphore(value: 0)
let decoder: JSONDecoder = JSONDecoder()
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
rowData = data
semaphore.signal()
}
task.resume()
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
do {
let user: User = try decoder.decode(User.self, from: rowData)
return user
} catch let e {
print("JSON Decode Error :\(e)")
fatalError()
}
}
関数の呼び出し元で検索クエリを入れてもらい、該当するユーザーIDと先頭が一致するユーザーの情報が複数APIから返ってきます。
最初素人考えで、複数返してくるのだから、ループ文書かないとダメなのか? と思っていましたが、
構造体でItemをネストした構造体として、Itemsという配列の一要素にしているので、
上記のコードで上手いことやってくれます。
Codableすごい!
無理やりキャストしていた例だと、必要な値にたどりつくまで、
地獄みたいな段階を踏んでいましたが、この関数で返しているUserを使うと、シンプルに書けます。
class ViewModel {
var logins = [String]()
//~~~~~~~中略~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
func setUserInformation(user: User) -> () {
logins = user.items.map { $0.login }
}