讀取專案裡的檔案測試 JSON 解碼
為了測試 JSON 資料對應的 Decodable(或 Codable) 型別是否有問題,除了實際串接網路 API 抓資料,我們也可以先將 JSON 檔加入專案裡,方便沒有網路時進行測試。
JSON 檔存放的地方
JSON 檔加入專案時,依據不同的用途可以放在以下三種地方。
1. 加到 Preview Assets 或 Preview Content 下
只有在測試 App 時使用,它們將不會包含在我們製作的上架 App 裡。
2. 加到 Assets
3. 加到 Project navigator 下
以下我們參考 Apple 官方 Landmarks 範例的 function load 解析 JSON。
定義 function load
依據 JSON 檔放的位置,有兩種不同的寫法。
- 當 JSON 檔放在 asset
從 NSDataAsset 讀取。
func load<T: Decodable>(_ filename: String) -> T {
guard let data = NSDataAsset(name: filename)?.data else {
fatalError("Couldn't load \(filename) from asset")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
- 當 JSON 檔放在 Project navigator 下
從 Bundle 讀取。
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
使用 function load 解析 JSON
假設 JSON 檔放在 Preview Assets.xcassets,名字是 taylorSwift。
JSON 對應的 Codable 型別如下。
struct SearchResponse: Codable {
let resultCount: Int
let results: [StoreItem]
}
struct StoreItem: Codable {
let artistName: String
let trackName: String
let collectionName: String?
let previewUrl: URL
let artworkUrl100: URL
let trackPrice: Double?
let releaseDate: Date
let isStreamable: Bool?
}
由於以上例子裡 releaseDate 的型別是 Date,所以我們必須調整 function load,設定 JSONDecoder 的 dateDecodingStrategy。
func load<T: Decodable>(_ filename: String) -> T {
guard let data = NSDataAsset(name: filename)?.data else {
fatalError("Couldn't load \(filename) from asset")
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
點選 button 時呼叫 function load,由於 searchResponse 的型別宣告為 SearchResponse,所以 function 知道要回傳的資料型別是 SearchResponse,因此知道要將 JSON 解碼成型別 SearchResponse 。
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
let searchResponse: SearchResponse = load("taylorSwift")
print(searchResponse)
}, label: {
Text("decode json")
})
}
}