利用 JSONDecoder 和 Codable 解析 JSON 和生成自訂型別資料
抓取網路上的 JSON 資料並不是太困難的事,但是如果想要解析它,甚至是把它變成方便 App 使用的自訂型別,卻需要寫許多程式碼才能實現。因此從前一些解析 JSON 和將 JSON 變成自訂型別的第三方套件大受歡迎,不過在 Swift 4,有了 JSONDecoder 和 Codable,這些第三方套件可以通通丟掉了 ! (ps: JSONDecoder 要 iOS 7 以上才支援)
接下來就讓我們串接一些有趣的 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 { var id: Int var image: URL var 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。(ps: 若時間是特別的格式,請將型別宣告為 String,之後再另外轉成 Date)
- value 是整數 ➪ Int
- value 是浮點數 ➪ Float 或 Double
- value 是字串 ➪ String
- value 是 true 或 false ➪ Bool
- value 是陣列 ➪ Array
- value 是 object ➪ 遵從 protocol Decodable 或 Codable 的自訂型別。
- key 不一定會出現時 ➪ 型別要宣告為 optional 或給預設值。
ps: 依據以上規則,我們將能處理大部分的 JSON 資料,只有少數 case 要特別處理(比方網址包含中文或是特別的時間格式)。
因此下圖 object 的 key 將成為 Meme 的 property。

struct Meme: Codable { var id: Int var image: URL var caption: String var 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 { var id: String var image: URL var caption: String var category: String}
(4) 只要定義 App 需要的欄位就好,因此自訂型別的 property 可以少訂,不用將 JSON dictionary 的每個 key 都寫成 property。
比方以下 JSON 的 object 有 4 個 key,不過我們的 App 只需要其中的部分資訊,所以我們在 Meme 裡只宣告 3 個 property。

struct Meme: Codable { var id: Int var image: URL var 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。
struct Song: Codable { var artistName: String var trackName: String var collectionName: String? var previewUrl: URL var artworkUrl100: URL var trackPrice: Double? var releaseDate: Date var isStreamable: Bool?}struct SongResults: Codable { var resultCount: Int var results: [Song]}
從以下兩張 JSON 的解析圖,我們可看出它的第一層是 object,object key results 的資料是 array,array 裡裝了一首首好聽的歌曲 object。


因此我們定義了型別 SongResults & Song 對應第一層的 object & 歌曲 object,並將 property results 的型別宣告為 [Song],說明它是 array。
struct SongResults: Codable { var resultCount: Int var results: [Song] }
值得注意的,Song 的部分 property 被宣告為 optional,因為 JSON 裡有些 key 不會每一筆資料都有,比方不是每首歌都可以買,所以不一定有 trackPrice。因此我們將不一定會有的 collectionName,trackPrice & isStreamable 設為 optional。如果沒有設為 optional,到時候一旦發現沒有這些欄位,將造成轉換失敗。
struct Song: Codable { var artistName: String var trackName: String var collectionName: String? var previewUrl: URL var artworkUrl100: URL var trackPrice: Double? var releaseDate: Date var isStreamable: Bool?}
除了宣告為 optional,我們也可以給預設值。比方將 trackPrice 設為 0.99,如此當 JSON 的歌曲沒有價錢時,它的價錢將被設為 0.99。
var trackPrice = 0.99抓取 JSON 資料,將它變成自訂型別 SongResults。
let urlStr = "https://itunes.apple.com/search?term=swift&media=music"if let url = URL(string: urlStr) { let task = 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") } } task.resume()}
利用 JSONDecoder 的 function decode 將 Data 型別的 JSON 資料變成 SongResults 型別資料。
其中 dateDecodingStrategy 特別設為 .iso8601,因為時間有多種不同格式,詳情可參考 DateDecodingStrategy 的說明。
當 JSON 的第一層是 array
掌握剛剛提到的方法,我們已經具備解析大部分 JSON 資料的能力。不過如果 JSON 的第一層是 array,不是 dictionary 呢 ? 比方以下抓取可愛貓咪的 API。
[{"breeds":[],"id":"6ph","url":"https://cdn2.thecatapi.com/images/6ph.jpg","width":3072,"height":4608}]這時候我們只有一個小地方要調整,只要在 decode 時傳入的型別加上 [ ] 即可,例如以下例子:
struct Cat: Decodable { let id: String let url: URL}let urlStr = "https://api.thecatapi.com/v1/images/search?format=json&limit=10"if let url = URL(string: urlStr) { var request = URLRequest(url: url) request.setValue("DEMO-API-KEY", forHTTPHeaderField: "x-api-key") let task = URLSession.shared.dataTask(with: url) { (data, response , error) in let decoder = JSONDecoder() if let data = data, let cats = try? decoder.decode([Cat].self, from: data) { print(cats) } } task.resume()}
找出 JSONDecoder 的 decode 錯誤
不過我們都知道寫程式時常會遇到問題,沒有問題才是不正常的。如果發現 decode 一直失敗的話, 可參考以下連結的說明。
關於 JSONDecoder 和 Codable 的相關說明,有興趣的朋友,可進一步參考以下連結
