HW#45 JSON Decoder

這次的練習主要是練習如何解析JSON的資料,並建構以下內容為這次練習的API(打星號,是稍微複雜一點的API資料庫):

  • REST Country 🌟
  • REST GitHubs
  • Trivia API
  • Apple Marketing RSS
  • Random User Generator 🌟
  • Joker API
  • iTunes API 🌟
  • YouBike API

這次練習的內容,主要是為了練習如何解析大量的JSON file,下列內容是參考彼得潘的文章內容做學習。

練習主題:

心得:

我一開始是不太知道該如何進行JSON的解析,後來把API 網址丟到Postman之後,且透過閱讀其他同學的文章以及大量的練習,還有請Ai幫忙檢查,才知道該怎麼做、問題在哪裡!

最主要的,仍是透過大量的練習,才知道問題可以怎麼被解決、該如何解、以及如何處理資料!

Country:

  • Website:
  • API:
 https://restcountries.com/v3.1/all
  • 資料解析:

Country的JSON資料結構,我一開始做覺得算是蠻複雜的,如果一開始不熟悉,蠻容易印不資料內容來,但最主要就是要分清楚括號之間的差異,最主要就是要分清楚是Int / Double / String / Date / URL

首先,要先把API網頁貼到Postman裡面的GET欄位,並按出send,並選擇HTTPS裡面的GET method去找到資料內容。

基本上要先了解資料結構本身,才有辦法知道要如何解析完整的資料結構,在這次的練習當中,由於每個資料結構的內容都不同,所以必須要瞭解資料結構內容,才有辦法解析資料。

取得資料後,再來就是要定義資料結構,首先要先認識括號差異,例如:方括弧(square brackets) 花括弧 (curly brackets)的差異,以及冒號前後之間的內容,找到資料前後對應的關係。

可以先以一小串的資料做為練習內容:

[
{
"name": {
"common": "Cyprus",
"official": "Republic of Cyprus",
"nativeName": {
"ell": {
"official": "Δημοκρατία της Κύπρος",
"common": "Κύπρος"
},
"tur": {
"official": "Kıbrıs Cumhuriyeti",
"common": "Kıbrıs"
}
}
},
"tld": [
".cy"
],
"cca2": "CY",
"ccn3": "196",
"cca3": "CYP",
"cioc": "CYP",
"independent": true,
"status": "officially-assigned",
"unMember": true,
"currencies": {
"EUR": {
"name": "Euro",
"symbol": "€"
}
},
"idd": {
"root": "+3",
"suffixes": [
"57"
]
},
"capital": [
"Nicosia"
],
"altSpellings": [
"CY",
"Kýpros",
"Kıbrıs",
"Republic of Cyprus",
"Κυπριακή Δημοκρατία",
"Kıbrıs Cumhuriyeti"
],
"region": "Europe",
"subregion": "Southern Europe",
"languages": {
"ell": "Greek",
"tur": "Turkish"
},
"translations": {
"ara": {
"official": "جمهورية قبرص",
"common": "قبرص"
},
"bre": {
"official": "Republik Kiprenez",
"common": "Kiprenez"
},
"ces": {
"official": "Kyperská republika",
"common": "Kypr"
},
"cym": {
"official": "Gweriniaeth Cyprus",
"common": "Cyprus"
},
"deu": {
"official": "Republik Zypern",
"common": "Zypern"
},
"est": {
"official": "Küprose Vabariik",
"common": "Küpros"
},
"fin": {
"official": "Kyproksen tasavalta",
"common": "Kypros"
},
"fra": {
"official": "République de Chypre",
"common": "Chypre"
},
"hrv": {
"official": "Republika Cipar",
"common": "Cipar"
},
"hun": {
"official": "Ciprusi Köztársaság",
"common": "Ciprus"
},
"ita": {
"official": "Repubblica di Cipro",
"common": "Cipro"
},
"jpn": {
"official": "キプロス共和国",
"common": "キプロス"
},
"kor": {
"official": "키프로스 공화국",
"common": "키프로스"
},
"nld": {
"official": "Republiek Cyprus",
"common": "Cyprus"
},
"per": {
"official": "جمهوری قبرس",
"common": "قِبرِس"
},
"pol": {
"official": "Republika Cypryjska",
"common": "Cypr"
},
"por": {
"official": "República de Chipre",
"common": "Chipre"
},
"rus": {
"official": "Республика Кипр",
"common": "Кипр"
},
"slk": {
"official": "Cyperská republika",
"common": "Cyprus"
},
"spa": {
"official": "República de Chipre",
"common": "Chipre"
},
"srp": {
"official": "Кипарска Република",
"common": "Кипар"
},
"swe": {
"official": "Republiken Cypern",
"common": "Cypern"
},
"tur": {
"official": "Kıbrıs Cumhuriyeti",
"common": "Kıbrıs"
},
"urd": {
"official": "جمہوریہ قبرص",
"common": "قبرص"
},
"zho": {
"official": "塞浦路斯共和国",
"common": "塞浦路斯"
}
},
"latlng": [
35.0,
33.0
],
"landlocked": false,
"area": 9251.0,
"demonyms": {
"eng": {
"f": "Cypriot",
"m": "Cypriot"
},
"fra": {
"f": "Chypriote",
"m": "Chypriote"
}
},
"flag": "🇨🇾",
"maps": {
"googleMaps": "https://goo.gl/maps/77hPBRdLid8yD5Bm7",
"openStreetMaps": "https://www.openstreetmap.org/relation/307787"
},
"population": 1207361,
"gini": {
"2018": 32.7
},
"fifa": "CYP",
"car": {
"signs": [
"CY"
],
"side": "left"
},
"timezones": [
"UTC+02:00"
],
"continents": [
"Europe"
],
"flags": {
"png": "https://flagcdn.com/w320/cy.png",
"svg": "https://flagcdn.com/cy.svg",
"alt": "The flag of Cyprus has a white field, at the center of which is a copper-colored silhouette of the Island of Cyprus above two green olive branches crossed at the stem."
},
"coatOfArms": {
"png": "https://mainfacts.com/media/images/coats_of_arms/cy.png",
"svg": "https://mainfacts.com/media/images/coats_of_arms/cy.svg"
},
"startOfWeek": "monday",
"capitalInfo": {
"latlng": [
35.17,
33.37
]
},
"postalCode": {
"format": "####",
"regex": "^(\\d{4})$"
}
},
{
"name": {
"common": "Eritrea",
"official": "State of Eritrea",
"nativeName": {
"ara": {
"official": "دولة إرتريا",
"common": "إرتريا‎"
},
"eng": {
"official": "State of Eritrea",
"common": "Eritrea"
},
"tir": {
"official": "ሃገረ ኤርትራ",
"common": "ኤርትራ"
}
}
},
"tld": [
".er"
],
"cca2": "ER",
"ccn3": "232",
"cca3": "ERI",
"cioc": "ERI",
"independent": true,
"status": "officially-assigned",
"unMember": true,
"currencies": {
"ERN": {
"name": "Eritrean nakfa",
"symbol": "Nfk"
}
},
"idd": {
"root": "+2",
"suffixes": [
"91"
]
},
"capital": [
"Asmara"
],
"altSpellings": [
"ER",
"State of Eritrea",
"ሃገረ ኤርትራ",
"Dawlat Iritriyá",
"ʾErtrā",
"Iritriyā"
],
"region": "Africa",
"subregion": "Eastern Africa",
"languages": {
"ara": "Arabic",
"eng": "English",
"tir": "Tigrinya"
},
"translations": {
"ara": {
"official": "دولة إريتريا",
"common": "إريتريا"
},
"bre": {
"official": "Stad Eritrea",
"common": "Eritrea"
},
"ces": {
"official": "Stát Eritrea",
"common": "Eritrea"
},
"cym": {
"official": "Gwladwriaeth Eritrea",
"common": "Eritrea"
},
"deu": {
"official": "Staat Eritrea",
"common": "Eritrea"
},
"est": {
"official": "Eritrea Riik",
"common": "Eritrea"
},
"fin": {
"official": "Eritrean valtio",
"common": "Eritrea"
},
"fra": {
"official": "État d'Érythrée",
"common": "Érythrée"
},
"hrv": {
"official": "Država Eritreji",
"common": "Eritreja"
},
"hun": {
"official": "Eritrea",
"common": "Eritrea"
},
"ita": {
"official": "Stato di Eritrea",
"common": "Eritrea"
},
"jpn": {
"official": "エリトリア国",
"common": "エリトリア"
},
"kor": {
"official": "에리트레아국",
"common": "에리트레아"
},
"nld": {
"official": "Staat Eritrea",
"common": "Eritrea"
},
"per": {
"official": "جمهوری اریتره",
"common": "اریتره"
},
"pol": {
"official": "Państwo Erytrea",
"common": "Erytrea"
},
"por": {
"official": "Estado da Eritreia",
"common": "Eritreia"
},
"rus": {
"official": "Государство Эритрея",
"common": "Эритрея"
},
"slk": {
"official": "Eritrejský štát",
"common": "Eritrea"
},
"spa": {
"official": "Estado de Eritrea",
"common": "Eritrea"
},
"srp": {
"official": "Држава Еритреја",
"common": "Еритреја"
},
"swe": {
"official": "Staten Eritrea",
"common": "Eritrea"
},
"tur": {
"official": "Eritre Devleti",
"common": "Eritre"
},
"urd": {
"official": "ریاستِ ارتریا",
"common": "ارتریا"
},
"zho": {
"official": "厄立特里亚",
"common": "厄立特里亚"
}
},
"latlng": [
15.0,
39.0
],
"landlocked": false,
"borders": [
"DJI",
"ETH",
"SDN"
],
"area": 117600.0,
"demonyms": {
"eng": {
"f": "Eritrean",
"m": "Eritrean"
},
"fra": {
"f": "Érythréenne",
"m": "Érythréen"
}
},
"flag": "🇪🇷",
"maps": {
"googleMaps": "https://goo.gl/maps/HRyqUpnPwwG6jY5j6",
"openStreetMaps": "https://www.openstreetmap.org/relation/296961"
},
"population": 5352000,
"fifa": "ERI",
"car": {
"signs": [
"ER"
],
"side": "right"
},
"timezones": [
"UTC+03:00"
],
"continents": [
"Africa"
],
"flags": {
"png": "https://flagcdn.com/w320/er.png",
"svg": "https://flagcdn.com/er.svg",
"alt": "The flag of Eritrea comprises three triangles — a large red isosceles triangle with its base spanning the hoist end and its apex at the midpoint on the fly end, and a green and blue right-angled triangle above and beneath the red triangle. On the hoist side of the red triangle is a golden vertical olive branch encircled by a golden olive wreath."
},
"coatOfArms": {
"png": "https://mainfacts.com/media/images/coats_of_arms/er.png",
"svg": "https://mainfacts.com/media/images/coats_of_arms/er.svg"
},
"startOfWeek": "monday",
"capitalInfo": {
"latlng": [
15.33,
38.93
]
}
},
  • Structure:
import UIKit

// 定義結構體以匹配 JSON 結構
struct Country: Codable {
let name: Name
let tld: [String]?
let cca2: String?
let ccn3: String?
let cca3: String?
let cioc: String?
let independent: Bool?
let unMember: Bool?
let currencies: [String: Currency]?
let idd: Idd
let capital: [String]?
let altSpellings: [String]?
let region: String
let subregion: String?
let languages: [String: String]?
let translations: [String: Translation]?
let latlng: [Double]?
let landlocked: Bool
let area: Double
let demonyms: [String: Demonym]?
let flag: String
let maps: Maps
let population: Int
let gini: [String: Double]?
let fifa: String?
let car: Car
let timezones, continents: [String]
let flags: ImageURLs
let coatOfArms: ImageURLs
let startOfWeek: String
let capitalInfo: CapitalInfo
let postalCode: PostalCode?
}

struct Name: Codable {
let common, official: String
let nativeName: [String: NativeName]?
}

struct NativeName: Codable {
let official, common: String
}

struct Currency: Codable {
let name: String
let symbol: String?
}

struct Idd: Codable {
let root: String?
let suffixes: [String]?
}

struct Translation: Codable {
let official: String?
let common: String?
}

struct Demonym: Codable {
let f, m: String
}

struct Maps: Codable {
let googleMaps: String?
let openStreetMaps: String?
}

struct Car: Codable {
let signs: [String]?
let side: String
}

struct ImageURLs: Codable {
let png: String?
let vg: String?
}

struct CapitalInfo: Codable {
let latlng: [Double]?
}

struct PostalCode: Codable {
let format: String?
let regex: String?
}
  • FetchData:

運用URLSession.shared.dataTask,找到這API網址的資料,並且運用do catch的寫法,處理error handling,當API資料被找到的時候,do的部分會被執行,到catch時,則印出error,最後一定要記得加上.resume(),才會開始執行任務。(細節可以參考下列彼得潘老師的文章內容)

// 解析 JSON 資料的函數
func fetchCountryData() {
guard let url = URL(string: "https://restcountries.com/v3.1/all") else { return }

URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print(error?.localizedDescription ?? "Unknown error")
return
}
do {
let decoder = JSONDecoder()
let countries = try decoder.decode([Country].self, from: data)
// 現在可以使用解析後的國家資料了
// 例如,列印每個國家的首都和面積
for country in countries {
print("\(country.name.common): \(country.capital?.first ?? "No capital"), Area: \(country.area) km²")
}
} catch {
print("Decoding error: \(error)")
}
}.resume()
}

// 呼叫函數以啟動解析過程
fetchCountryData()
  • Result:

在function裡面,運用for in的方法,試著印出Country裡面的name的common內容,資料就會產生在Debugger console裡面。

  • 加上Optional

之前有因為沒有加上冒號,而導致在Playground裡面印不出資料,所以要在定義好的資料型別後方加上冒號 Optional(例如: string > string?),因為不確定這個API網址裡面,有沒有這筆資料。

Screen shot in Playground.

GitHubs:

這次是拿Peter Pan文章裡面的練習內容,是保羅大大的在GitHubs粉絲的追蹤數,找到followers的id名稱 / 追蹤人數 / 名字等等資料。

  • API Address:
// GitHubs API
https://api.github.com/users/twostraws/followers
  • Postman:

把資料丟進Postman裡面,一樣使用同樣的方法用HTTP Method的GETS,並將資料產出。

Screen shot for Postman.
  • Structure:

裡面有些內容很明顯就是網址內容,這時候可以直接定義成URL。

struct GitHubs: Codable {
let login: String
let id: Int
let node_id: String
let avatar_url: URL
let gravatar_id: String
let url: String
let html_url: String
let followers_url: URL
let following_url: URL
let gists_url: URL
let starred_url: URL
let subscriptions_url: URL
let organizations_url: URL
let repos_url: URL
let events_url: URL
let received_events_url: URL
let type: String
let site_admin: Bool
}
  • Fetch Data:

也是運用同樣的寫法,將資料印出!

func fetchGitHubsData () {
guard let url = URL(string: "https://api.github.com/users/twostraws/followers") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print(error?.localizedDescription ?? "Unknown error")
return
}
do {
let decoder = JSONDecoder()
let followerList = try decoder.decode([GitHubs].self, from: data)
for followerData in followerList {
print("The login is: \(followerData.login)")
print("The id is: \(followerData.id)")
print("The node_id is: \(followerData.node_id)")
print("The avatar_url is: \(followerData.avatar_url)")
print("The gravatar_id is: \(followerData.gravatar_id)")
print("The url is: \(followerData.url)")
print("The html_url is: \(followerData.html_url)")
print("The followers_url is: \(followerData.followers_url)")
print("The following_url is: \(followerData.following_url)")
print("The gists_url is: \(followerData.gists_url)")
print("The starred_url is: \(followerData.starred_url)")
print("The subscriptions_url is: \(followerData.subscriptions_url)")
print("The organizations_url is: \(followerData.organizations_url)")
print("The repos_url is: \(followerData.repos_url)")
print("The events_url is: \(followerData.events_url)")
print("The received_events_url is: \(followerData.received_events_url)")
print("The type is: \(followerData.type)")
print("The site_admin is: \(followerData.site_admin)")
print("")
}
} catch {
print("Decoding error: \(error)")
}
}.resume()
}
  • Result:
Screen shot in Playground.

TRIVIA:

Trivia是個選擇題為主的API內容,主要是練習印出來的內容為何~

  • API Address:
 https://opentdb.com/api.php?amount=10
  • Postman:
Screen shot for Postman.
  • Structure
import UIKit

struct Trivia: Codable {
let response_code: Int?
let results: [TriviaData]
}

struct TriviaData: Codable {
let type: String
let difficulty: String
let category: String
let question:String
let correct_answer: String
let incorrect_answers: [String]
}
  • Fetch Data:
func fetchData () {
guard let url = URL(string: "https://opentdb.com/api.php?amount=10") else {
return
}
URLSession.shared.dataTask(with: url) { data, responde, error in
guard let data = data else {
print(error?.localizedDescription ?? "Unknown error")
return
}
do {
let decoder = JSONDecoder()
let trivia = try decoder.decode(Trivia.self, from: data)
if let firstQuestion = trivia.results.first {

print(firstQuestion.question)
print(firstQuestion.type)
print(firstQuestion.difficulty)
print(firstQuestion.category)
print(firstQuestion.question)
print(firstQuestion.correct_answer)
print(firstQuestion.incorrect_answers)

} else {
print("No question found")
}
} catch {
print("Decoding error: \(error)")
}
}.resume()
}

fetchData()
  • Result:
Screen shot in Playground.

Apple Markting RSS:

這個內容是運用Apple Marketing的RSS的訂閱,產生出有關Apple Music的相關內容。

  • API Address:
https://rss.applemarketingtools.com/api/v2/tw/music/most-played/10/songs.json
  • Postman:
Screen shot for Postman.
  • Structure:
import UIKit

struct Apple: Decodable {
let feed: Feed
}

struct Feed: Decodable {
let title: String
let id: String
let author: Author
let links: [Link]?
let copyright: String
let country: String
let icon: URL
let updated: String
let results: [Result]
}

struct Author: Decodable {
let name: String
let url: URL
}

struct Link: Decodable {
let `self`: URL
}

struct Result: Decodable {
let artistName: String
let id: String
let name: String
let releaseDate: Date
let kind: String
let artistId: String
let artistUrl: URL
let artworkUrl100: URL
let genres: [Genre]
let url: URL
}

struct Genre: Decodable {
let genreId: String
let name: String
let url: URL
}
  • Fetch Data:
func fetchData () {
guard let url = URL(string: "https://rss.applemarketingtools.com/api/v2/tw/music/most-played/10/songs.json") else { return }

URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print(error?.localizedDescription ?? "Can't get the data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let appleData = try decoder.decode(Apple.self, from: data)
print(appleData.feed.title)
print(appleData.feed.id)
print(appleData.feed.author)
print(appleData.feed.copyright)
print(appleData.feed.country)
print(appleData.feed.icon)
print(appleData.feed.results)

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

fetchData()
  • Result:
Screen shot in Playground.

Random User Generator:

  • API Address:
 https://randomuser.me/api/
  • Postman:
Screen shot for Postman.
  • Structure:
import UIKit

struct RandomUserData: Decodable {
let results: [Results]
let info: Info
}

struct Results: Decodable {
let gender: String
let name: Name // 不太確定,應該是Dict
let location: Location
let email: String
let login: Login
let registered: Registered
let phone: String
let cell: String
let id: Id
let picture: Picture
let nat: String
}

struct Name: Decodable {
let title: String
let first: String
let last: String
}

struct Location: Decodable {
let street: Street?
let city: String
let state: String
let country: String
let postcode: Int?
let coordinates: Coordinates
let timezone: Timezone
}

struct Street: Decodable {
let number: Int
let name: String
}

struct Coordinates: Decodable {
let latitude: String
let longitude: String
}

struct Timezone: Decodable {
let offset: String
let description: String
}

struct Login: Decodable {
let uuid: String
let username: String
let password: String
let salt: String
let md5: String
let sha1: String
let sha256: String
}

struct Registered: Decodable{
let date: Date
let age: Int
}

struct Id: Decodable {
let name: String
let value: String?
}

struct Picture: Decodable {
let large: URL
let medium: URL
let thumbnail: URL
}

struct Info: Decodable {
let seed: String
let results: Int
let page: Int
let version: String
}
  • Fetch Data:
func fetchRandomUserData() {
guard let url = URL(string: "https://randomuser.me/api/") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print(error ?? "Can't get the data" )
return
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let randomUserData = try decoder.decode(RandomUserData.self, from: data)
print(randomUserData.info.page)
print(randomUserData.info.seed)
print(randomUserData.info.results)
print(randomUserData.info.version)

} catch {
print("資料錯誤:\(error)")
}
}.resume()
}

fetchRandomUserData()
  • Result:

Joker API:

  • API Address:
 https://v2.jokeapi.dev/joke/Any
  • Postman:
Screen shot for Postman.
  • Structure:
struct Joker: Decodable {
let error: Bool
let category: String
let type: String
let joke: String?
let flags: Flags
let id: Int
let safe: Bool
let lang: String
}

struct Flags: Decodable {
let nsfw: Bool
let religious: Bool
let political: Bool
let racist: Bool
let sexist: Bool
let explicit: Bool
}
  • Fetch Data:
func fetchJoke() {
guard let url = URL(string: "https://v2.jokeapi.dev/joke/Any") else {
print("Invalid URL")
return
}

URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print(error?.localizedDescription ?? "Can't get the data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let jokeData = try decoder.decode(Joker.self, from: data)
print(jokeData.error)
print(jokeData.id)
} catch {
print("Decoding error: \(error)")
}
}.resume()
}

fetchJoke()
  • Result:
Screen shot in Playground.

iTunes:

  • API Address:
 https://itunes.apple.com/search?term=jack+johnson&limit=25
  • Postman:
Screen shot for Postman.
  • Structure:
import UIKit

struct Itunes: Decodable {
let resultCount: Int
let results: [Results]
}

struct Results: Decodable {
let wrapperType: String
let kind: String
let artistName: String
let collectionName: String
let trackName: String
let collectionCensoredName: String
let trackCensoredName: String
let collectionArtistId: Int
let collectionArtistViewUrl: URL
let collectionViewUrl: URL
let trackViewUrl: URL
let previewUrl: URL
let artworkUrl30: URL
let artworkUrl60: URL
let collectionPrice: Double
let trackPrice: Double
let trackRentalPrice: Double
let collectionHdPrice: Double
let trackHdPrice: Double
let trackHdRentalPrice: Double
let releaseDate: Date
let collectionExplicitness: String
let trackExplicitness: String
let discCount: Int
let discNumber: Int
let trackCount: Int
let trackNumber: Int
let trackTimeMillis: Int
let country: String
let currency: String
let primaryGenreName: String
let contentAdvisoryRating: String
let shortDescription: String
let longDescription: String
let hasITunesExtras: Bool
}
  • Fetch Data:

func fetchData () {
let urlString = "https://itunes.apple.com/search?term=jack+johnson&limit=25"
guard let url = URL(string: urlString) else {
print("Invalid URL")
return
}

let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching data: \(error.localizedDescription)")
return
}

guard let data = data else {
print("No data returned from URL")
return
}

do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let itunesData = try decoder.decode(Itunes.self, from: data)
print("Result Count: \(itunesData.resultCount)")
if let firstResult = itunesData.results.first {
print("First Result Name: \(firstResult.trackName)")
}
} catch {
print("Error decoding JSON: \(error)")
}
}
task.resume()
}


fetchData()
  • Result:

YouBike API:

這個是在台北市資料開放平臺的資料,可以找到政府所公開的資料,例如:垃圾車的到站時間、Youbike等等,有興趣可以這網站看看!

  • API Address:
 https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json
  • Postman:
Screen shot for Postman.
  • Structure:
struct YouBikeData: Decodable {
let sno: String
let sna: String
let tot: Int
let sbi: Int
let sarea: String
let mday: String
let lat: Double
let lng: Double
let ar: String
let sareaen: String
let aren: String
let bemp: Int
let act: String
let srcUpdateTime: String
let updateTime: String
let infoTime: String
let infoDate: String
}
  • Fetch Data:
func fetchData () {
guard let url = URL(string: " https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "Can't get the data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let youbikeDataset = try decoder.decode([YouBikeData].self, from: data)

if let firstStation = youbikeDataset.first {
print("DEBUG PRINT: Youbike資料:\(firstStation.bemp)")
print("DEBUG PRINT: 所有的Youbike區域:\(firstStation.sareaen)")
print("DEBUG PRINT: 目前Youbike的車位總數資料\(firstStation.tot)")
}
} catch {
print(error)
}
}
task.resume()
}

fetchData()
  • Result:

📝 筆記:

在這次練習除了瞭解到如何運用Decode做資料分析、還有認識新的程式寫法,Error handling / do catch的用法 / Closure等等,認識網路這塊之後,才知道說如何透過程式抓到資料,覺得實在有趣!

  • Codable / Decodable / Encodable:

此外,我後來想知道Codable & Decodable & Encodable之間的差異,講白話一點,Decodable就是解碼、Encodable就是編碼,而Codable就是這兩個protocol的集合,兩者都是專門處理JSON的方法!

我個人認為在Stackoverflow上的解釋還蠻好懂的,可以作為參考 :)

  • Dicitonary or Array?

當資料外圍是中括號 [] 的在解析型別時需加上 [] 標註為 Array,資料外圍是花括號 {} 則是 Dictionary,型別上不需特別備註。

下列為Apple Marketing RSS的練習一小部分,就是看出Genre為部分的解析內容,而到了Result的時候,就變成一個Array,這時候就要回頭看一下JSON裡面的內容為何。

struct Result: Decodable {
let artistName: String
let id: String
let name: String
let releaseDate: Date
let kind: String
let artistId: String
let artistUrl: URL
let artworkUrl100: URL
let genres: [Genre]
let url: URL
}

struct Genre: Decodable {
let genreId: String
let name: String
let url: URL
}

當時在練習Country API的時候,就有很多貌似Array,但實際上是dictionary,所以一定要看清楚!

  • 型別定義:

如果是整個 JSON 資料皆被中括號 [ ] 框住( Array),則在進行 decode 動作時,記得框住整筆資料(可以參考youbike的例子):

let youbikeDataset = try decoder.decode([YouBikeData].self, from: data)
  • 型別設置有誤。

Double/String 的話很有可能是沒有解碼到日期格式,這時候要確定是否有無設置decoder.dateDecodingStrategy

  • 相同型別在struct裡面的兩種寫法:

後面在練習的時候,有發現到,如果是相同屬性的資料結構,我們可以運用命名的後面加上逗號,以減少寫code的數量,前提是命名的內容都要是可以被找到的內容,不然其實仍是要拆開來寫的。

舉例:

一般寫法:

struct NativeName: Codable {
let official: String
let common: String
}

簡潔:

struct NativeName: Codable {
let official, common: String
}

My GitHubs:

--

--