Hello everyone,
I am trying to learn a little about using URLSession and getting a JSON file and download it and decode it.
I keep getting an error telling me that the JSON is corrupted or bad, I have verified the the data is good and valid JSON so I'm not sure why.
If anyone could test this I would be greatful, I don't see what's wrong with the JSON
Here is the small project I've been working with to test with
import SwiftUI
struct ContentView: View {
let url = URL(string: "https://github.com/tazmancoder/MyAppStoreApps/blob/main/AppleAppStoreApps.json")
var body: some View {
VStack {
Image(systemName: "square.and.arrow.down")
.imageScale(.large)
.foregroundColor(.accentColor)
.padding()
Button {
fetchDataAndDecode(from: url!)
} label: {
Text("Download My Apps")
}
.buttonStyle(.bordered)
}
.padding()
}
func fetchDataAndDecode(from url: URL) {
Task {
do {
let appData = try await URLSession.shared.decode(AppData.self, from: url)
} catch let DecodingError.keyNotFound(key, context) {
fatalError("Failed to decode due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
} catch let DecodingError.typeMismatch(_, context) {
fatalError("Failed to decode due to type mismatch – \(context.debugDescription)")
} catch let DecodingError.valueNotFound(type, context) {
fatalError("Failed to decode due to missing \(type) value – \(context.debugDescription)")
} catch DecodingError.dataCorrupted(_) {
fatalError("Failed to decode because it appears to be invalid JSON")
} catch {
fatalError("Failed to decode \(url): \(error.localizedDescription)")
}
}
}
}
My JSON Model
// MARK: - Welcome
struct AppData: Codable {
let resultCount: Int
let results: [Result]
}
// MARK: - Result
struct Result: Codable {
let wrapperType: String
let artistType: String?
let artistName: String
let artistID: Int
let artistViewURL: String?
let features, supportedDevices: [String]?
let kind: String?
let trackViewURL: String?
let trackContentRating, minimumOSVersion, trackCensoredName: String?
let languageCodesISO2A: [String]?
let fileSizeBytes, formattedPrice, contentAdvisoryRating: String?
let currentVersionReleaseDate: Date?
let releaseNotes, primaryGenreName: String?
let primaryGenreID: Int?
let sellerName, description: String?
let genreIDS: [String]?
let bundleID: String?
let isVppDeviceBasedLicensingEnabled: Bool?
let trackID: Int?
let trackName: String?
let releaseDate: Date?
let version, currency: String?
let price: Double?
let genres: [String]?
let userRatingCount: Int?
enum CodingKeys: String, CodingKey {
case wrapperType, artistType, artistName
case artistID = "artistId"
case artistViewURL = "artistViewUrl"
case features, supportedDevices, kind
case trackViewURL = "trackViewUrl"
case trackContentRating
case minimumOSVersion = "minimumOsVersion"
case trackCensoredName, languageCodesISO2A, fileSizeBytes, formattedPrice, contentAdvisoryRating, currentVersionReleaseDate, releaseNotes, primaryGenreName
case primaryGenreID = "primaryGenreId"
case sellerName, description
case genreIDS = "genreIds"
case bundleID = "bundleId"
case isVppDeviceBasedLicensingEnabled
case trackID = "trackId"
case trackName, releaseDate, version, currency, price, genres, userRatingCount
}
}
Extension on URLSession
extension URLSession {
func decode<T: Decodable>(
_ type: T.Type = T.self,
from url: URL,
keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .deferredToData,
dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate
) async throws -> T {
print("URL: \(url)")
let (data, _) = try await data(from: url)
print("Data: \(data.description)")
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = keyDecodingStrategy
decoder.dataDecodingStrategy = dataDecodingStrategy
decoder.dateDecodingStrategy = dateDecodingStrategy
let decoded = try decoder.decode(T.self, from: data)
return decoded
}
}