利用JSONDecoder 解析口罩剩餘數量API - Medium

文章推薦指數: 80 %
投票人數:10人

成功將JSON 變成裝著Mask 資料的array 後,我們可以將它顯示在App 的表格畫面,不過若想顯示在地圖上則有點困難,因為JSON 裡並沒有包含經緯度。

當然我們 ... UpgradeOpeninappHomeNotificationsListsStoriesWritePublishedin彼得潘的SwiftiOSApp開發問題解答集利用JSONDecoder解析口罩剩餘數量API感謝大大們開發了口罩剩餘數量的API,讓我們可以開發查詢口罩數量的網頁和App,接下來就讓我們試試串接政府資料開放平臺API&kiang大大提供的藥局+衛生所即時庫存,利用JSONDecoder解析API回傳的JSON資料。

口罩供需資訊平台-HackMDEditdescriptiong0v.hackmd.io一.政府資料開放平臺API連到健保特約機構口罩剩餘數量明細清單的說明網頁健保特約機構口罩剩餘數量明細清單經本平臺品質檢測後符合表格化資料之資料資源將會轉為CSV、XLSX、ODS、XML、JSON檔案格式,若所轉出之資料內容與機關所提供之原始資料有所出入,請以機關所提供之原始資料為主。

data.gov.tw點選檢視資料複製JSON下載連結https://quality.data.gov.tw/dq_download_json.php?nid=116285&md5_url=2150b333756e64325bdbc4a5fd45fad1利用Postman測試口罩剩餘數量API回傳的JSON資料[{"醫事機構代碼":"0145080011","醫事機構名稱":"衛生福利部花蓮醫院豐濱原住民分院","醫事機構地址":"花蓮縣豐濱鄉豐濱村光豐路41號","醫事機構電話":"(03)8358141","成人口罩剩餘數":"544","兒童口罩剩餘數":"293","來源資料時間":"2020/02/1415:55:05"},{"醫事機構代碼":"0291010010","醫事機構名稱":"連江縣立醫院","醫事機構地址":"連江縣南竿鄉復興村217號","醫事機構電話":"(083)623995","成人口罩剩餘數":"0","兒童口罩剩餘數":"84","來源資料時間":"2020/02/1415:55:05"},定義對應JSON資料的Decodable型別structMask:Decodable{letid:Stringletname:Stringletaddress:Stringlettel:StringletadultCount:StringletchildCount:Stringlettime:DateenumCodingKeys:String,CodingKey{caseid="醫事機構代碼"casename="醫事機構名稱"caseaddress="醫事機構地址"casetel="醫事機構電話"caseadultCount="成人口罩剩餘數"casechildCount="兒童口罩剩餘數"casetime="來源資料時間"}}由於JSON資料的欄位名稱是中文,所以我們利用enumCodingKeys客製JSON對應的property,將中文名轉換成方便程式使用的英文名字。

利用enumCodingKeys客製JSON對應的property利用JSONDecoder我們可以方便地串接API,將網路上的JSON資料轉換成方便App使用的自訂型別。

medium.com另外我們將時間的欄位定義成Date型別,方便未來程式做時間相關的運算,因此待會我們還要撰寫將時間字串轉換成Date的程式。

利用JSONDecoder解析口罩API的JSON資料ifleturl=URL(string:"https://quality.data.gov.tw/dq_download_json.php?nid=116285&md5_url=2150b333756e64325bdbc4a5fd45fad1"){URLSession.shared.dataTask(with:url){(data,response,error)inletdecoder=JSONDecoder()letdateFormatter=DateFormatter()dateFormatter.dateFormat="yyyy/MM/ddHH:mm:ss"decoder.dateDecodingStrategy=.formatted(dateFormatter)ifletdata=data,letmasks=try?decoder.decode([Mask].self,from:data){print(masks)}}.resume()}利用JSONDecoder解析口罩API回傳的JSON資料。

利用JSONDecoder和Codable解析JSON和生成自訂型別資料抓取網路上的JSON資料並不是太困難的事,但是如果想要解析它,甚至是把它變成方便App使用的自訂型別,卻需要寫許多程式碼才能實現。

因此從前一些解析JSON和將JSON變成自訂型別的第三方套件大受歡迎,不過在Swift…medium.com為了將特殊格式的時間字串轉換成Date,我們額外設定JSONDecoder的dateDecodingStrategy,利用enumDateDecodingStrategy的caseformatted(DateFormatter)。

JSONDecoder.DateDecodingStrategy.formatted(_:)Editdescriptiondeveloper.apple.com由於JSON的時間格式是2020/02/1415:55:05,因此我們在formatted裡傳入DateFormatter物件,將它的格式設為yyyy/MM/ddHH:mm:ss。

letdateFormatter=DateFormatter()dateFormatter.dateFormat="yyyy/MM/ddHH:mm:ss"decoder.dateDecodingStrategy=.formatted(dateFormatter)關於yyyy/MM/ddHH:mm:ss的相關說明可參考以下連結。

NSDateFormatter.comnsdateformatter.comiswrittenwithSwift4.2,asameanstolearnopen-sourceSwift,theSwiftPackageManager,and…nsdateformatter.com值得注意的,App畫面若要顯示時間,也要撰寫利用DateFormatter將Date轉換成字串的程式,例如以下程式:letdateFormatter:DateFormatter={letformatter=DateFormatter()formatter.dateFormat="yyyy-MM-ddHH:mm:ss"returnformatter}()overridefunctableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{guardletcell=tableView.dequeueReusableCell(withIdentifier:"MaskTableViewCell",for:indexPath)as?MaskTableViewCellelse{returnUITableViewCell()}letmask=masks[indexPath.row]lettimeText=dateFormatter.string(from:mask.time)cell.updateLabel.text=timeTextreturncell}結果自訂init(from:),將adultCount&childCount變Int如果想要adultCount&childCount變成方便程式比大小的整數,我們必須在Mask裡自訂init(from:),自己實作JSON資料的解析。

structMask:Decodable{letid:Stringletname:Stringletaddress:Stringlettel:StringletadultCount:IntletchildCount:Intlettime:DateenumCodingKeys:String,CodingKey{caseid="醫事機構代碼"casename="醫事機構名稱"caseaddress="醫事機構地址"casetel="醫事機構電話"caseadultCount="成人口罩剩餘數"casechildCount="兒童口罩剩餘數"casetime="來源資料時間"}init(fromdecoder:Decoder)throws{letcontainer=trydecoder.container(keyedBy:CodingKeys.self)id=trycontainer.decode(String.self,forKey:.id)name=trycontainer.decode(String.self,forKey:.name)address=trycontainer.decode(String.self,forKey:.address)tel=trycontainer.decode(String.self,forKey:.tel)adultCount=Int(trycontainer.decode(String.self,forKey:.adultCount))??0childCount=Int(trycontainer.decode(String.self,forKey:.childCount))??0time=trycontainer.decode(Date.self,forKey:.time)}}成功將JSON變成裝著Mask資料的array後,我們可以將它顯示在App的表格畫面,不過若想顯示在地圖上則有點困難,因為JSON裡並沒有包含經緯度。

當然我們可以自力自強,努力地利用CLGeocoder將地址轉換成經緯度,但這需要花費大量的時間,等轉換好口罩已經被買完了。

沒關係,我們還有第二條路,採用kiang大大提供的藥局+衛生所即時庫存。

二.kiang提供的藥局+衛生所即時庫存API網址和JSON分析API網址如下https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.jsonJSON資料如下:{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"5901020554","name":"師大藥局","phone":"02-23623479","address":"台北市大安區師大路99號1樓","mask_adult":0,"mask_child":40,"updated":"2020\/02\/1422:59:03","available":"星期一上午看診、星期二上午看診、星期三上午看診、星期四上午看診、星期五上午看診、星期六上午看診、星期日上午休診、星期一下午看診、星期二下午看診、星期三下午看診、星期四下午看診、星期五下午看診、星期六下午看診、星期日下午休診、星期一晚上看診、星期二晚上看診、星期三晚上看診、星期四晚上看診、星期五晚上看診、星期六晚上看診、星期日晚上休診","note":"-","custom_note":"","website":"","county":"臺北市","town":"大安區","cunli":"古風里","service_periods":"NNNNNNYNNNNNNYNNNNNNY"},"geometry":{"type":"Point","coordinates":[121.528509,25.02271]}},其中特別值得一提的欄位是coordinates&service_periods。

coordinates:代表經緯度,第一個數字代表經度,第二個數字代表緯度。

有了經緯度,我們將可開發出口罩地圖App。

service_periods:代表看診星期,有21位元,1~7為每周一至周日上午開診情形,8~14為每周一至周日下午開診情形,15~21為每周一至周日晚上開診情形,N=開診,Y=休診,因此NNNNNNYNNNNNNYNNNNNNY表示星期日整天休診,其它時間開診。

全民健康保險特約院所固定服務時段欄位說明:1.「看診星期」:共計21位元,其中1~7為每周一至周日上午時段開診情形、8~14為每周一至周日下午時段開診情形、15~21為每周一至周日晚上時段開診情形。

"N"=開診、"Y"=休診。

…data.gov.tw定義對應JSON資料的Decodable型別structMaskData:Decodable{letfeatures:[Mask]structMask:Decodable{letproperties:Propertiesletgeometry:Geometry}structProperties:Decodable{letname:Stringletphone:Stringletaddress:StringletmaskAdult:IntletmaskChild:Intletupdated:Dateletavailable:Stringletnote:StringletcustomNote:Stringletwebsite:Stringletcounty:Stringlettown:Stringletcunli:StringletservicePeriods:String}structGeometry:Decodable{letcoordinates:[Double]}}JSON裡像mask_adult&mask_child之類帶底線的名字被改成Swift習慣的maskAdult&maskChild,待會我們將透過將JSONDecoder的keyDecodingStrategy設為.convertFromSnakeCase做轉換。

decoder.keyDecodingStrategy=.convertFromSnakeCase利用JSONDecoder解析JSONextensionDateFormatter{staticletcustomFormatter:DateFormatter={letformatter=DateFormatter()formatter.dateFormat="yyyy/MM/ddHH:mm:ss"returnformatter}()}ifleturl=URL(string:"https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json"){URLSession.shared.dataTask(with:url){(data,response,error)inletdecoder=JSONDecoder()decoder.keyDecodingStrategy=.convertFromSnakeCasedecoder.dateDecodingStrategy=.custom({(decoder)->DateinlettimeString=trydecoder.singleValueContainer().decode(String.self)returnDateFormatter.customFormatter.date(from:timeString)??Date()})ifletdata=data,letmaskData=try?decoder.decode(MaskData.self,from:data){print(maskData.features)}}.resume()}由於JSON的updated欄位有可能是空字串,因此我們不能使用formatted(DateFormatter)將字串轉換成Date。

我們改用enumDateDecodingStrategy的casecustom((Decoder)throws->Date)做轉換,當轉換失敗時直接回傳目前的時間Date()。

JSONDecoder.DateDecodingStrategy.custom(_:)Editdescriptiondeveloper.apple.com此外為了效率我們將負責轉換的DateFormatter物件儲存在static常數customFormatter,這樣才不會在轉換每一筆資料時重新生成DateFormatter物件。

Onemorething,利用MKGeoJSONDecoder解析GeoJSON剛剛kiang提供的藥局+衛生所即時庫存資料是一種稱為GeoJSON格式的資料,我們其實可使用iOS13的MKGeoJSONDecoder解析,有興趣的朋友可參考以下製作口罩地圖App的範例說明。

利用MKGeoJSONDecoder解析GeoJSON&製作口罩地圖App認識GeoJSONmedium.com201Morefrom彼得潘的SwiftiOSApp開發問題解答集彼得潘和學生們在開發iOSApp路上曾經解決的問題集Readmorefrom彼得潘的SwiftiOSApp開發問題解答集Getstarted彼得潘的iOSAppNeverland4.8KFollowers彼得潘的iOSApp程式設計入門,文組生的iOSApp程式設計入門講師,彼得潘的Swift程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.comFollowRelatedPassingtheBaton — WhattoDointheWater?|sinbilan:tmllyung?KoodoMakingBillingunderstandableTechniquesToColorizeMonochrome(BlackAndWhite)ImagesCS371pSpring2022:GuanLinWee(Week4)Weekof7Feb — 13FebHelpStatusWritersBlogCareersPrivacyTermsAboutKnowable



請為這篇文章評分?