利用 convertFromSnakeCase 將有底線的 JSON key 對應到無底線的 property
利用 JSONDecoder,現在我們不再需要第三方套件,很方便就能將 JSON 資料轉換成自訂型別。
不過有時我們會遇到一個問題。後台回傳的 JSON 欄位名稱可能以 snake case 方式命名,以 _ 分隔單字,比方 love_song。這和 Swift 習慣的 camel case 命名有很大差異。( camel case 將單字的第一個字母大寫,幫助我們分辨每個單字,比方 loveSong。)
當我們遇到剛剛的問題時,使用 JSONDecoder 解析 JSON 會比較麻煩,需要另外寫程式轉換。比方以下例子,我們利用 API 抓取 NASA 拍到的美麗照片。
https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY
它回傳的 JSON 資料如下,其中的 media_type & service_version 都加了底線。
假設我們自訂的 NASA 資料型別如下
struct NasaPicture: Codable {
let url: URL
let mediaType: String
}
由於欄位 mediaType 和 原本 JSON 的欄位 media_type 不一致,若沒特別處理,以下程式解析 JSON 時將發生錯誤。
- 方法 1: 使用 async & await (Xcode 13 以上)
let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
let nasaPicture = try decoder.decode(NasaPicture.self, from: data)
print(nasaPicture)
} catch {
print(error)
}
}
- 方法 2: 使用 completion handler
let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!
URLSession.shared.dataTask(with: url) { data, response , error in
if let data {
let decoder = JSONDecoder()
do {
let nasaPicture = try decoder.decode(NasaPicture.self, from: data)
print(nasaPicture)
} catch {
print(error)
}
} else if let error {
print(error)
}
}.resume()
錯誤訊息告訴我們它找不到 mediaType。
keyNotFound(CodingKeys(stringValue: "mediaType", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"mediaType\", intValue: nil) (\"mediaType\").", underlyingError: nil))
這個問題其實可以解,只是有點麻煩,必須搭配 CodingKey protocol,另外撰寫轉換的程式。
在 Swift 4.1,我們終於可以輕鬆轉換了 ! JSONDecoder 多了一個新的 property,KeyDecodingStrategy 型別的 keyDecodingStrategy,透過它可控制 JSON 欄位名稱的轉換。
open var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy
我們只要將 keyDecodingStrategy 設為 .convertFromSnakeCase,JSONDecoder 即可聰明地將 snake case 的欄位名稱變成 camel case !
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase