利用 JSONDecoder 和 Codable 解析 JSON 和生成自訂型別資料

抓取網路上的 JSON 資料並不是太困難的事,但是如果想要解析它,甚至是把它變成方便 App 使用的自訂型別,卻需要寫許多程式碼才能實現。因此從前一些解析 JSON 和將 JSON 變成自訂型別的第三方套件大受歡迎,不過在 Swift 4,有了 JSONDecoder 和 Codable,這些第三方套件可以通通丟掉了 !

接下來就讓我們串接一些有趣的 API 試試 JSONDecoder 的威力吧。

隨機的 meme 圖片

API 的網址如下

https://some-random-api.ml/meme

後台回傳的資料如下

{"id":2,"image":"https://i.some-random-api.ml/VM2Dkpp7VX.png","caption":"Hmm I wonder if this was planned","category":"math"}

利用網站 JSON Editor Online 顯示後截圖如下:

定義型別 Meme。

struct Meme: Codable {   let id: Int   let image: URL   let caption: String}

利用 JSONDecoder,我們可以將網路上抓下來的 JSON 資料從 Data 型別變成自訂型別 Meme。至於 Meme 是如何定義出來的,接下來就讓我們一步步示範說明吧。

(1) 將 JSON 裡的 object 變成自訂型別,可以用 class,也可以用 struct 定義,型別名字可自取。

因此我們將下圖的 object 變成型別 Meme。

struct Meme {}

(2) 讓自訂型別遵從 protocol Decodable 或 Codable。

JSONDecoder 可以將資料從 Data 型別變成遵從 protocol Decodable 或 Codable 的自訂型別,Decodable 表示可解碼,Codable 表示可解碼跟編碼。

struct Meme: Codable {}

(3) JSON 裡 object 的 key 將成為自訂型別的 property, 而 property 的型別則由 key 對應的 value 型別決定。

property 可宣告為變數,也可宣告為常數,可依據以下幾點規則決定 property 的型別:

  • value 是網址 ➪ URL。(ps: 如果網址包含 ASCII 以外的文字,請將型別宣告為 String,之後再另外轉成 URL)
  • value 是時間 ➪ Date。
  • value 是整數 ➪ Int
  • value 是浮點數 ➪ Float 或 Double
  • value 是字串 ➪ String
  • value 是 true 或 false ➪ Bool
  • value 是陣列 ➪ Array
  • value 是 object ➪ 遵從 protocol Decodable 或 Codable 的自訂型別。
  • key 不一定會出現或 key 的 value 可能為 null 時 ➪ 型別要宣告為 optional。

ps: 依據以上規則,我們將能處理大部分的 JSON 資料,只有少數 case 要特別處理(比方網址包含中文或是特別的時間格式)。

因此下圖 object 的 key 將成為 Meme 的 property。

struct Meme: Codable {   let id: Int   let image: URL   let caption: String   let category: String}

值得注意的,名稱和型別不對都會造成轉換失敗。

  • property 名稱和 JSON 裡 object 的 key 名字要一樣。

比方 image 寫成 images 會有問題。

struct Meme: Codable {   var id: Int   var images: URL   var caption: String   var category: String}

不過需要的話,其實有特別的方法可以讓它們不一樣,詳情可參考以下連結。

  • property 型別錯誤將轉換失敗。

property 型別錯誤將轉換失敗,比方 id 是整數,但你卻將型別宣告為 String。

struct Meme: Codable {   let id: String   let image: URL   let caption: String   let category: String}

(4) 只要定義 App 需要的欄位就好,因此自訂型別的 property 可以少訂,不用將 JSON dictionary 的每個 key 都寫成 property。

比方以下 JSON 的 object 有 4 個 key,不過我們的 App 只需要其中的部分資訊,所以我們在 Meme 裡只宣告 3 個 property。

struct Meme: Codable {   let id: Int   let image: URL   let caption: String}

(5) 以下兩種情況 property 要宣告為 optional。

(a) JSON object 不一定有的 key 。

(b) JSON object 某個 key 對應的 value 有可能是 null。

如果沒有設為 optional,到時候一旦發現這些欄位有問題,將造成轉換失敗。

了解比較簡單的 meme 例子後,接著讓我們挑戰更複雜的 iTunes API。

iTunes 音樂

我們就以從 iTunes 搜尋 swift 當例子,API 的網址為

https://itunes.apple.com/search?term=swift&media=music

後台回傳的資料利用網站 JSON Editor Online 顯示後截圖如下:

依據 iTunes 回傳的 JSON ,我們定義型別 SongResults & Song。如以下兩張圖片所示,我們可將 SongResults & Song 分開定義,也可將 Song 定義在 SongResults 裡。

從以下兩張 JSON 的解析圖,我們可看出它的第一層是 object,object key results 的資料是 array,array 裡裝了一首首好聽的歌曲 object。

因此我們定義了型別 SongResults & Song 對應第一層的 object & 歌曲 object,並將 property results 的型別宣告為 [Song],說明它是 array。

struct SongResults: Codable {   let resultCount: Int   let results: [Song]  }

值得注意的,Song 的部分 property 被宣告為 optional,因為 JSON 裡有些 key 不會每一筆資料都有,比方不是每首歌都可以買,所以不一定有 trackPrice。因此我們將不一定會有的 collectionName,trackPrice & isStreamable 設為 optional。如果沒有設為 optional,到時候一旦發現沒有這些欄位,將造成轉換失敗。

struct Song: Codable {   let artistName: String   let trackName: String   let collectionName: String?   let previewUrl: URL   let artworkUrl100: URL   let trackPrice: Double?   let releaseDate: Date   let isStreamable: Bool?}

抓取 JSON 資料,將它變成自訂型別 SongResults。

let urlStr = "https://itunes.apple.com/search?term=swift&media=music"if let url = URL(string: urlStr) {    URLSession.shared.dataTask(with: url) { (data, response , error) in        let decoder = JSONDecoder()        decoder.dateDecodingStrategy = .iso8601        if let data = data, let songResults = try?  
decoder.decode(SongResults.self, from: data)
{ for song in songResults.results { print(song) } } else { print("error") } }.resume()}

利用 JSONDecoder 的 function decode 將 Data 型別的 JSON 資料變成 SongResults 型別資料。

其中 dateDecodingStrategy 特別設為 .iso8601,因為時間有多種不同格式,詳情可參考 DateDecodingStrategy 的說明。

iTunes API 補充

  • 以 country 指定某個國家的 music store
https://itunes.apple.com/search?term=周杰倫&media=music&country=TW
  • 抓取大圖

假設 JSON 的圖片網址如下

https://is3-ssl.mzstatic.com/image/thumb/Music/v4/76/54/ea/7654ea84-2830-26d3-1526-01833cfe68fc/source/100x100bb.jpg

我們可以調整圖片的尺寸,比方將 100x100bb.jpg 改成 500x500bb.jpg。

https://is3-ssl.mzstatic.com/image/thumb/Music/v4/76/54/ea/7654ea84-2830-26d3-1526-01833cfe68fc/source/500x500bb.jpg

當 JSON 的第一層是 array

掌握剛剛提到的方法,我們已經具備解析大部分 JSON 資料的能力。不過如果 JSON 的第一層是 array,不是 dictionary 呢 ? 比方以下抓取 Dcard 文章的 API。

https://dcard.tw/_api/posts 取得的 Dcard 不分類最新文章 JSON 如下,第一層是 array,array 裡的成員是每篇文章的相關資訊。

這時候我們只有一個小地方要調整,只要在 decode 時傳入的型別加上 [ ] 即可,例如以下例子:

struct Post: Decodable {
let title: String
}
let urlStr = "https://dcard.tw/_api/posts"
if let url = URL(string: urlStr) {
URLSession.shared.dataTask(with: url) { (data, response , error) in
let decoder = JSONDecoder()
if let data = data, let posts = try? decoder.decode([Post].self, from: data) {
print(posts)
}
}.resume()
}

找出 JSONDecoder 的 decode 錯誤

不過我們都知道寫程式時常會遇到問題,沒有問題才是不正常的。如果發現 decode 一直失敗的話, 可參考以下連結的說明。

透過 init(from:) 解析 JSON 裡包裝成字串的 array 或 dictionary

定義 Decodable 的 init(from:) 解析 JSON

貼上 JSON,自動產生 Swift Codable 型別的 quicktype

關於 JSONDecoder 和 Codable 的相關說明,有興趣的朋友,可進一步參考以下連結

彼得潘的 Swift iOS App 開發問題解答集

彼得潘和學生們在開發 iOS App 路上曾經解決的問題集

彼得潘的 iOS App Neverland

Written by

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com

彼得潘的 Swift iOS App 開發問題解答集

彼得潘和學生們在開發 iOS App 路上曾經解決的問題集

彼得潘的 iOS App Neverland

Written by

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com

彼得潘的 Swift iOS App 開發問題解答集

彼得潘和學生們在開發 iOS App 路上曾經解決的問題集

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store