從 API 的 response 中把我們所需的資料整理好吧!
當我在接案和隕石開發的期間,有時候在跟一些後端合作的時候,常常發現打完 API 後的 response 會無法理解,有時候是回傳不知是什麼用途的欄位,或是回傳一個 Number 或 Bool 值,但沒有特別描述他的目的。當然還有應該要開成 Object 的欄位開成 Array;而應該要 Array 的欄位開成 Object 這種可怕的事情...。(但我遇過最離奇的應該還是 key 是繁體中文,但是大陸的案子就是了)
因此,我覺得如何把這些錯綜復雜的資料整理是一門學問,我們應該擷取我們需要的資訊,把它整理成我們好理解的方式,並且能夠快速套用在畫面上。
這篇文章主要是介紹一下如何解析後端回傳的 response,並且透過一些簡單的設置,把資料變成我們想要的方式,讓我們之後再串接資料到畫面上時能夠更輕鬆。
當我們在串接 API 後,你的 JSON 資料可能會像是:
這種方式其實非常不容易理解,也不容易看出其階層關係。因此,當我們想要查看某個 API 的 JSON 結構時,我們可以透過一些工具來把我們的 JSON 整理成容易理解的結構。
你可以到 chrome 線上應用程式商店的擴充功能找到一些整理 JSON 的工具,能夠將網頁上的 JSON 直接顯示為易讀的結構。
你可以到 Google 搜尋 JSON Editor 來找到一些線上整理 JSON 的網站,這種網站我們只要將我們的 JSON 放到左邊,接著按轉換,右邊就會出現整理後的結構了。而且我們還可以點選箭頭來顯示\隱藏結構階層內的內容:
確認好結構之後,現在我們需要把 JSON 的結構轉成我們城市內所需的結構,這邊我們有很多方式可以解析,但是就不是推薦使用原生的 JSONSerialization
來做,因為他的解析過程非常麻煩,我們在之後會稍微提到。這邊我們推薦兩種方式,一種是原生的 Codable 協議以及第三方程序庫 SwiftyJSON。
之後我們會用這個 好想工作室書架 API 來做範例:
https://bookshelf.goodideas-studio.com/api
利用 Codable 可以讓我們快速將轉換自身結構為外部表示的類型,其中有 Decodable 跟 Encodable 協議。我們可以使用 Decodable 將 JSON 解析成我們的結構,而 Encodable 是能將資料編碼成我們要的結構(一個是解析,一個是編碼)。
這邊我們簡單示範如何使用 Codable 解析上面書架的結構:
而我們使用 Deoodable 只要定義成相同的結構,並且每個屬性的類型相同,最後讓他遵循 Decodable
協議即可,若有部分不同則解析失敗。因此,我們定義如下:
接著讓我們使用 JSONDecoder
來解析他,並且印出 list
裡面的所有資料吧:
這邊在定義時,屬性名稱必須與 key 值名稱相同才能夠解析正確。如果想要定義不同的屬性名稱則需要使用
CodingKeys
來輔助。
而我們有一個叫 QuickType 的網站可以幫我們快速生成這個結構,而且還會幫你寫好 CodingKeys
,它也會幫忙處理可能 nil 或是同一屬性不同類型的值。但是因為是機器產生的,所以有些地方還是稍微需要調整:
這邊 Decodable
解析的部分先告一段落,後面再補充。
因為 SwiftyJSON 是第三方程式庫,所以記得使用
pod install
安裝它。
而使用 SwiftyJSON 來解析的話,比較麻煩的是需要寫個 init(json:)
來初始化每個屬性值,而我們可以透過 JSON
這個類型透過 key 來實例相對的值:
這邊因為我們 List 裡面也是一個物件,所以一樣定義了一個
List
的struct
,一樣在裡面加了一個init(json:)
的初始化方式。並且在Bookshelf
的初始化器中透過map
實例化array
中的每個元素。
接著我們一樣試著印出 list
中的每個值來確認是否串接成功吧:
這邊 SwiftyJSON 比較方便的是,我們可以使用 JSON.xxxValue
來轉換成特定值的屬性。
有時候 JSON 上的屬性名稱並不是我們想要的(或是亂碼),因此,我們需要將它更改我們想要的名稱:
Codable 的話我們可以透過 CodingKey
來定義它的 key 是對應哪個值,所以我們在定義結構的時候可以使用別的名稱。若是想保持原本的名稱就不必寫後面的 rawValue
:
而 SwiftyJSON 就沒差了,因為原本屬性名稱就不用跟 key 值相同,因此我們可以任意更改屬性名稱。
nil
或可能“沒有”的值Codable 中我們只要在該屬性類型後面加上 "?",將類型標記為 Optional Type 就能夠處理這種狀況:
而 SwiftyJSON 中我們將類型轉換成特定的 value 時,如果發生值為 nil
或值不存在時,則會給一個 default 值,像是字串會變為空字串,數字則會為 0,不需額外處理。
我們可以新增一個 init(decoder:)
的方式,在初始化的時候將值轉換為我們想要的類型:
而在 SwiftJSON 中,我們可以在該 JSON
類型後加上各種 value
來轉化成各種你想要的類型:
但這邊類型轉換有個雷就是了,SwiftyJSON 背後的轉換過程就是使用 NS (NeXTSTEP) 類型來做轉換的,如下列這段方式:
NSString(string: "123").integerValue
NSNumber(integerLiteral: 123).stringValue
但是特殊字串在轉數字類型時就需要注意了,某些字串因為有特殊符號,在轉換的過程中可能會有問題,下面舉幾個範例給大家參考:
所以有特殊符號的需求,可能比較安全的方式就是直接抓 stringValue
下來做一些 replace 之類的操作再轉換成數字,最後再輸出時在轉回原本的字串格式可能比較不會出錯。
這邊兩者做法皆相同,就不特別分類了~
有時候我們需要一些獲得 API 後才會知道的屬性,而且這個屬性可能會在獲取資料後才能夠計算出來,這時候我們計算屬性就能夠派上用場,我們可以把新增的屬性加入我們的結構中,計算出我們所需的資訊。例如:在這邊我們想要算出書籍原價與售價的折扣 % 數,這時我們就能夠建立一個屬性來計算:
這邊順便判斷一下折扣是否落在 0...1 的區間內,不然資料可能會有意外的折扣%數。
接著我們直接運行程式即可,因為我們是透過其他屬性所計算出來的(想接也沒得接),並且印出剛剛所新建的 discount
:
在開發中,有時候我們需要某些值來判斷某些事情,但是如果這時候後端是回傳數字或是字串時,雖然判斷這些不是一件難事,但就是不太好看,也總是要判斷 default
的其他狀況。
下面我們舉一個範例,假設後端會回傳:
0
or 1
讓你判斷會員是否認證male
或 female
讓你判斷性別0
, 1
, 2
, -1
, 99
分別為,普通會員、中級會員、高級會員、封鎖用戶跟管理者。我們的 struct 的類型大致上如下:
而這時我們簡單寫個判斷可能會是:
switch member.certified {
case 0: // 未認證
case 1: // 已認證
default: // 意外狀況
}
switch member.sex {
case "male": // 男性
case "female": // 女性
default: // 意外狀況
}
switch member.status {
case 0: // 普通會員
case 1: // 中等會員
case 2: // 高級會員
case -1: // 封鎖用戶
case 99: // 管理者
default: // 意外狀況
}
這邊共同的狀況都是我們需要為了每個判斷都必須加上 default
條件,並且每次判斷數字會字串時可能無法馬上了解代表的意思,像是 status
跟 cerified
都是用數字判斷,可能會混淆。而 sex
這種字串又會有大小寫或是其他狀況,導致判斷錯誤。
因此我們需要一個方法把這些無意義的數據轉換成我們想要的類型。而在這邊,我會會使用 enum
的方式來處理這塊,我們將每個數據透過 rawValue
初始化為我們自訂的類型,讓我們在判斷時更加方便。
這邊我們將上面幾個屬性換成比較容易理解的類型吧!
我們為 Sex
與 Status
建立一個 enum
,並且設置每個 case
的 rawValue
,而 cerified
只需判斷 0
, 1
,因此我們可以使用 Bool
值處理。
而設置完成這些類型後,我們可以在 init 階段將我們獲取的資料轉換成我們想要的類型後再輸出為實例(這邊我們假設有接到資料):
而如果後端的回傳值不穩定時,我們也能為每個 enum 的 case 中新增一些例外的情況,如此一來我們就能在 enum
初始化失敗時,賦予默認值:
經過這番轉換之後,你可以發現我們調用時非常直觀,不需要再進行額外的轉換,也不需加上 default
,但如果你有加上我們上面的 .none
或 .unknown
其實也就相當於我們的 default
:
這邊因為懶得想後面打什麼,所以都加上
break
因此你可以很了解是判斷什麼狀況,而當你缺少某些情況時,Xcode 編輯器也會很貼心的幫你補上缺少的 case。
點下去之後 Xcode 將幫你補齊缺少的情況:
那麼以上就是跟大家分享一下我在接 API 時常常會處理的方式,有時候我們不是只要將上面的資料一模一樣的接下來,而是應該要把後端回傳的資料篩選、過濾且整理之後,輸出成我們想要的格式,那麼之後無論要判斷或是顯示在畫面上時,肯定會事半功倍。