讀取專案裡的檔案測試 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")
})
}
}

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com