真實世界的 JSON Decode

雖然這次 Apple 幫我們做出了 Codable 這個好用的 protocol,但上了戰場之後呢?

如果你已經試過 Swift 4 提供的 Codable protocol 之後,你應該發現 json decode 在 swift 中已經不像以前那麼不方便了,也不需要再經過 dictionary 的轉換拖慢 decode 速度(像是第三方解析 json 套件:SwiftyJSON)。但 Codable 真的這麼好用嗎?不知道你有沒有注意到,有時候 backend 會因為使用套件的關係,回傳的 json 中會有 {} (空的 json)或者 “”(空字串),這樣會造成 json decode 失敗。

本文專注於在不自己實作 Decodable protocol 中 init(from decoder: Decoder) throws 方法,如果習慣自己實作此 init method 的朋友,將可能不會遇到此問題。

本篇文章將會專注於處理特殊 json 回傳情況,不熟悉 Codable 的朋友,可以參考下面文章:

情況一:空的 json { }

假設我們有一個 json data 如下,title 是字串,其他兩個 cover 是 BookCover 類別,我們要從 json data 轉換成可以使用的物件,但可以看到 frontCover 回傳的是一個空的 json,這將造成物件轉換失敗。

失敗的訊息如下:

keyNotFound(__lldb_expr_148.BookCover.CodingKeys.text, Swift.DecodingError.Context(codingPath: [__lldb_expr_148.Book.(CodingKeys in _26B35E459B7D5969E8B4869C3A09F28B).frontCover], debugDescription: “No value associated with key text (\”text\”).”, underlyingError: nil))

原因是當我們在轉換 frontCover 時,找不到 title 這個 key 所致。當然,在一個空的 json 中,一定找不到任何 key。

解決方式

因為 frontCover 的型態是 optional 的 BookCover,所以在 decode 時,將會執行 decodeIfPresent<T> 來檢查是否為 nil,現在我們要來對有這個 function 的 KeyedDecodingContainer 做一些 extension。

首先要先建立一個 protocol 來處理回傳空 json 的情況,並且對 KeyedDecodingContainer 做一些 extension。

最後回到類別,並且讓他 conform protocol 即可

情況二:空的字串 “”

類似於上面的狀況,只是空的 json {} 變成空的字串 “”,這個情況下因為 Genre 是 String 資料型態的 enum,所以我們預設有一個 init?(rawValue: String) 的 init method,但如果我們傳入空的字串到 init method 中,就會失敗(回傳 nil)。

結語

以前自己在接 rails 丟的 api 時常常會看到這類情況發生,而當初使用 swiftyJSON 因為只要給一個字串做 subscript 即可拿到想要的值,方便好用且無腦。

但 Codable 這個 protocol 雖然讓解析 json 變得不需要再使用第三方套件,速度應該比轉成 dictionary 更快(沒經過驗證),但也可以看到他對於 json 中的 {}, null, “” 處理上變得更麻煩,如果沒有先確定 backend 回傳型態,一個不小心可能會整個炸掉。

其實這翻譯文章寫到一半我已經心很累,原本想說 Codable 會讓程式變得更乾淨,但現在看起來維護性上其實變得頗差,容錯率也變低,寫起來更麻煩。我想我應該會回去使用慢到炸的 SwiftyJSON 吧?

如果有更好的解法,歡迎留言討論ㄛ~~~

原文:

更新:

剛剛稍微翻了一下 Codable 的 source code,發現 apple 在定義這個 protocol 時實在很嚴謹,也幫我們處理掉很多複雜的事,比如:日期的轉換,支援各種模式的解析、錯誤訊息完整且嚴謹。

雖然 Codable 在使用上還有一點不方便的地方,不可否認的是它提供了我們解析 json 一個新的方式,相信未來會越來越好用(畢竟也才第一版)。剛剛稍微翻了一下 source code,想要找到我一直想要找的寫法,雖說找到了,但看不懂…….我哭。

有興趣更深入了解 Codable 的朋友請往:

想讀 swift source code 的請往:

更新2:會需要幫 decodeIfPresent<T> 這個 method 加點工會是在你不自己實作 init(from decoder: Decoder) throws 這個 method 時會遇到的(偷懶不想另外加一個 enum CodingKeys: CodingKey 跟實作 init(from decoder: Decoder) throws) 。如果你已經自己處理掉 decode 會遇到的各種狀況時,那你應該不會遇到上述的問題~