今天來介紹 Ktor Client 的最後一篇,我們該如何使用之前所介紹的 serialization 讓 API 回傳直接變成我們想要的物件格式!
由於需要 kotlinx.serialization 的相關知識,如果大家還沒看過的話可以參考一下前幾天的文章:
https://ithelp.ithome.com.tw/articles/10302034
ContentNegotiation
是 Ktor 的一個 plugin,主要是讓我們可以使用各個不一樣的 serialization 來把 API 轉換我們想要的物件,所以第一步我們要把這個 dependency 加到我們的專案中:
implementation("io.ktor:ktor-client-content-negotiation:2.1.0")
還記得什麼是 plugin 嗎?Ktor core 的設計主要是提供一個框架,大部分功能都是透過 plugin 的方式完成,而 Ktor 的 plugin 也是一個 gradle 的 dependency,並不是 gradle 的 plugin 喔!
跟 Ktor core 一樣,我們除了增加 ContentNegotiation
之外,也需要告訴 ContentNegotiation
我們想要用什麼方式來做 serialization,目前除了 json 之外共有以下幾種:
// json
implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.0")
// xml
implementation("io.ktor:ktor-serialization-kotlinx-xml:2.1.0")
// cbor
implementation("io.ktor:ktor-serialization-kotlinx-cbor:2.1.0")
使用也跟之前的 convention 蠻類似的,畢竟 ContentNegotiation
也是一個 plugin,所以只要在 HttpClient
的 lambda block 呼叫 install 就可以了:
val client = HttpClient(CIO) {
install(ContentNegotiation)
}
但我們還需要指定 ContentNegotiation
所指定的 serialization 是 json
而不是其他方法,所以在 install
這個 block 繼續新增我們的參數如下:
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json()
}
}
而 json
這個 block 當然也可以繼續做些設定,最常見的就是以下這三個屬性:
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
參數的意義如下:
prettyPrint
:print 的時候會有比較好的 format,預設為 false
。isLenient
:讓 parse 的時候不會因為資料格式不對造成錯誤,比如說 string 沒有用 "" 包起來等,這個可以先跟 backend 溝通一下是否有這個需求,有需要再加。ignoreUnknownKeys
:轉換時如果有無法 mapping 的 key 的時候,要不要視為錯誤,通常會設成 true 比較好做向下相容,或是可以把物件定義為比 API 回傳還要更小的結構,但也可以依據你的需求調整。接下來我們還是以之前所說的 https://api.github.com/users/<USERNAME>
為例,以下是一個回傳的結果:
{
login: "Jintin",
id: 3413874,
node_id: "MDQ6VXNlcjM0MTM4NzQ=",
avatar_url: "https://avatars.githubusercontent.com/u/3413874?v=4",
gravatar_id: "",
url: "https://api.github.com/users/Jintin",
html_url: "https://github.com/Jintin",
followers_url: "https://api.github.com/users/Jintin/followers",
following_url: "https://api.github.com/users/Jintin/following{/other_user}",
gists_url: "https://api.github.com/users/Jintin/gists{/gist_id}",
starred_url: "https://api.github.com/users/Jintin/starred{/owner}{/repo}",
subscriptions_url: "https://api.github.com/users/Jintin/subscriptions",
organizations_url: "https://api.github.com/users/Jintin/orgs",
repos_url: "https://api.github.com/users/Jintin/repos",
events_url: "https://api.github.com/users/Jintin/events{/privacy}",
received_events_url: "https://api.github.com/users/Jintin/received_events",
type: "User",
site_admin: false,
name: "Jintin",
company: null,
blog: "https://www.linkedin.com/in/jintin",
location: "Taipei, Taiwan",
email: null,
hireable: true,
bio: "Android GDE, husband and dad.
Love to build interesting things to make life easier.",
twitter_username: "JintinLin",
public_repos: 44,
public_gists: 2,
followers: 170,
following: 6,
created_at: "2013-01-29T06:30:00Z",
updated_at: "2022-08-15T09:29:11Z"
}
因為蠻多參數對我們目前都不太重要,我們可以先簡單定義如下的 User
class:
@Serializable
data class User(
val login: String,
val id: Long,
@SerialName("avatar_url")
val avatarUrl: String,
)
如果你的 config 沒有
ignoreUnknownKeys
的話,而有 key 無法對應的話會直接報錯喔!
一切都完成以後,使用就非常簡單了,只要直接宣告我們要的型別是 User
就可以囉!
val user: User = client.get("https://api.github.com/users/Jintin").body()
這邊埋一下伏筆,之後我們介紹 KSP 的時候,其中一個範例將會是使用 KSP 讓 Ktor 的使用變得像 Retrofit 一樣的簡單喔,敬請持續關注!