iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 5
0

從 API 的 response 中把我們所需的資料整理好吧!

? 隕石小故事

當我在接案和隕石開發的期間,有時候在跟一些後端合作的時候,常常發現打完 API 後的 response 會無法理解,有時候是回傳不知是什麼用途的欄位,或是回傳一個 Number 或 Bool 值,但沒有特別描述他的目的。當然還有應該要開成 Object 的欄位開成 Array;而應該要 Array 的欄位開成 Object 這種可怕的事情...。(但我遇過最離奇的應該還是 key 是繁體中文,但是大陸的案子就是了)
因此,我覺得如何把這些錯綜復雜的資料整理是一門學問,我們應該擷取我們需要的資訊,把它整理成我們好理解的方式,並且能夠快速套用在畫面上。

Overview

這篇文章主要是介紹一下如何解析後端回傳的 response,並且透過一些簡單的設置,把資料變成我們想要的方式,讓我們之後再串接資料到畫面上時能夠更輕鬆。


串接 API

|查看 JSON 結構

當我們在串接 API 後,你的 JSON 資料可能會像是:

這種方式其實非常不容易理解,也不容易看出其階層關係。因此,當我們想要查看某個 API 的 JSON 結構時,我們可以透過一些工具來把我們的 JSON 整理成容易理解的結構。

1. Google 擴充功能

你可以到 chrome 線上應用程式商店的擴充功能找到一些整理 JSON 的工具,能夠將網頁上的 JSON 直接顯示為易讀的結構。

2. JSON Editor

你可以到 Google 搜尋 JSON Editor 來找到一些線上整理 JSON 的網站,這種網站我們只要將我們的 JSON 放到左邊,接著按轉換,右邊就會出現整理後的結構了。而且我們還可以點選箭頭來顯示\隱藏結構階層內的內容:


|解析 JSON 結構

確認好結構之後,現在我們需要把 JSON 的結構轉成我們城市內所需的結構,這邊我們有很多方式可以解析,但是就不是推薦使用原生的 JSONSerialization 來做,因為他的解析過程非常麻煩,我們在之後會稍微提到。這邊我們推薦兩種方式,一種是原生的 Codable 協議以及第三方程序庫 SwiftyJSON

之後我們會用這個 好想工作室書架 API 來做範例:
https://bookshelf.goodideas-studio.com/api

#Codable

利用 Codable 可以讓我們快速將轉換自身結構為外部表示的類型,其中有 Decodable 跟 Encodable 協議。我們可以使用 Decodable 將 JSON 解析成我們的結構,而 Encodable 是能將資料編碼成我們要的結構(一個是解析,一個是編碼)。

這邊我們簡單示範如何使用 Codable 解析上面書架的結構:

而我們使用 Deoodable 只要定義成相同的結構,並且每個屬性的類型相同,最後讓他遵循 Decodable 協議即可,若有部分不同則解析失敗。因此,我們定義如下:

接著讓我們使用 JSONDecoder 來解析他,並且印出 list 裡面的所有資料吧:

這邊在定義時,屬性名稱必須與 key 值名稱相同才能夠解析正確。如果想要定義不同的屬性名稱則需要使用 CodingKeys 來輔助。

而我們有一個叫 QuickType 的網站可以幫我們快速生成這個結構,而且還會幫你寫好 CodingKeys,它也會幫忙處理可能 nil 或是同一屬性不同類型的值。但是因為是機器產生的,所以有些地方還是稍微需要調整:

這邊 Decodable 解析的部分先告一段落,後面再補充。


#SwiftyJSON

因為 SwiftyJSON 是第三方程式庫,所以記得使用 pod install 安裝它。

而使用 SwiftyJSON 來解析的話,比較麻煩的是需要寫個 init(json:) 來初始化每個屬性值,而我們可以透過 JSON 這個類型透過 key 來實例相對的值:

這邊因為我們 List 裡面也是一個物件,所以一樣定義了一個 Liststruct,一樣在裡面加了一個 init(json:) 的初始化方式。並且在 Bookshelf 的初始化器中透過 map 實例化 array 中的每個元素。

接著我們一樣試著印出 list 中的每個值來確認是否串接成功吧:

這邊 SwiftyJSON 比較方便的是,我們可以使用 JSON.xxxValue 來轉換成特定值的屬性。


|資料整理

1. 更改屬性名稱

有時候 JSON 上的屬性名稱並不是我們想要的(或是亂碼),因此,我們需要將它更改我們想要的名稱:

#Codable:

Codable 的話我們可以透過 CodingKey 來定義它的 key 是對應哪個值,所以我們在定義結構的時候可以使用別的名稱。若是想保持原本的名稱就不必寫後面的 rawValue

#SwiftyJSON

而 SwiftyJSON 就沒差了,因為原本屬性名稱就不用跟 key 值相同,因此我們可以任意更改屬性名稱。


2. 處理可能為 nil 或可能“沒有”的值

#Codable:

Codable 中我們只要在該屬性類型後面加上 "?",將類型標記為 Optional Type 就能夠處理這種狀況:

#SwiftyJSON

而 SwiftyJSON 中我們將類型轉換成特定的 value 時,如果發生值為 nil 或值不存在時,則會給一個 default 值,像是字串會變為空字串,數字則會為 0,不需額外處理。


3. 初始化為不同類型的值

#Codable

我們可以新增一個 init(decoder:) 的方式,在初始化的時候將值轉換為我們想要的類型:

#SwiftyJSON

而在 SwiftJSON 中,我們可以在該 JSON 類型後加上各種 value 來轉化成各種你想要的類型:

但這邊類型轉換有個雷就是了,SwiftyJSON 背後的轉換過程就是使用 NS (NeXTSTEP) 類型來做轉換的,如下列這段方式:

NSString(string: "123").integerValue
NSNumber(integerLiteral: 123).stringValue

但是特殊字串在轉數字類型時就需要注意了,某些字串因為有特殊符號,在轉換的過程中可能會有問題,下面舉幾個範例給大家參考:

所以有特殊符號的需求,可能比較安全的方式就是直接抓 stringValue 下來做一些 replace 之類的操作再轉換成數字,最後再輸出時在轉回原本的字串格式可能比較不會出錯。


4. 新增計算屬性

這邊兩者做法皆相同,就不特別分類了~

有時候我們需要一些獲得 API 後才會知道的屬性,而且這個屬性可能會在獲取資料後才能夠計算出來,這時候我們計算屬性就能夠派上用場,我們可以把新增的屬性加入我們的結構中,計算出我們所需的資訊。例如:在這邊我們想要算出書籍原價與售價的折扣 % 數,這時我們就能夠建立一個屬性來計算:

這邊順便判斷一下折扣是否落在 0...1 的區間內,不然資料可能會有意外的折扣%數。

接著我們直接運行程式即可,因為我們是透過其他屬性所計算出來的(想接也沒得接),並且印出剛剛所新建的 discount


5. 建立特殊 Enum 類型

在開發中,有時候我們需要某些值來判斷某些事情,但是如果這時候後端是回傳數字或是字串時,雖然判斷這些不是一件難事,但就是不太好看,也總是要判斷 default 的其他狀況。

下面我們舉一個範例,假設後端會回傳:

  • 0 or 1 讓你判斷會員是否認證
  • malefemale 讓你判斷性別
  • 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 條件,並且每次判斷數字會字串時可能無法馬上了解代表的意思,像是 statuscerified 都是用數字判斷,可能會混淆。而 sex 這種字串又會有大小寫或是其他狀況,導致判斷錯誤。

因此我們需要一個方法把這些無意義的數據轉換成我們想要的類型。而在這邊,我會會使用 enum 的方式來處理這塊,我們將每個數據透過 rawValue 初始化為我們自訂的類型,讓我們在判斷時更加方便。

這邊我們將上面幾個屬性換成比較容易理解的類型吧!

我們為 SexStatus 建立一個 enum,並且設置每個 caserawValue,而 cerified 只需判斷 0, 1,因此我們可以使用 Bool 值處理。

而設置完成這些類型後,我們可以在 init 階段將我們獲取的資料轉換成我們想要的類型後再輸出為實例(這邊我們假設有接到資料):

而如果後端的回傳值不穩定時,我們也能為每個 enum 的 case 中新增一些例外的情況,如此一來我們就能在 enum 初始化失敗時,賦予默認值:

經過這番轉換之後,你可以發現我們調用時非常直觀,不需要再進行額外的轉換,也不需加上 default,但如果你有加上我們上面的 .none.unknown 其實也就相當於我們的 default

這邊因為懶得想後面打什麼,所以都加上 break

因此你可以很了解是判斷什麼狀況,而當你缺少某些情況時,Xcode 編輯器也會很貼心的幫你補上缺少的 case。

點下去之後 Xcode 將幫你補齊缺少的情況:


Summary

那麼以上就是跟大家分享一下我在接 API 時常常會處理的方式,有時候我們不是只要將上面的資料一模一樣的接下來,而是應該要把後端回傳的資料篩選、過濾且整理之後,輸出成我們想要的格式,那麼之後無論要判斷或是顯示在畫面上時,肯定會事半功倍。


上一篇
D4 - 使用 IBDesignable 和 IBInspectable 快速製作 UI
下一篇
D6 - 讓我們在啟動畫面(Launch Screen)做一些怪怪的事吧
系列文
諸神黃昏下的 iOS 工程師31

尚未有邦友留言

立即登入留言