Search code examples
jsonswiftswiftuimvvmdeezer

Deezer API failing to decode?


I'm trying to access "Genre Categories" data from Deezer's API (url: https://api.deezer.com/genre) in my SwiftUI Project, however in the line where I decode and assign the data value to my array, it doesn't happen. Instead the program falls to my else condition. I've used this exact program structure on other API's ( Apple's iTunes API and other public test APIs) and it works with no problem whatsoever. Could there be a problem in Deezer API or my request itself?

My GenreModel Code: '''

import Foundation

struct Genre: Codable {
    let id: Int
    let name: String
    let picture: String
   
    
}

'''

My GenreViewModel Code:

'''

import Foundation

final class GenreViewModel: ObservableObject{
    
    @Published var genres: [Genre] = []
    @Published var hasError = false
    @Published var error: UserError?
    
    
    func fetchGenres(){
        hasError = false
        let genreUrl = "https://api.deezer.com/genre"
        
        if let url = URL(string: genreUrl){
            
            URLSession.shared.dataTask(with: url){ [weak self] data,response, error in
                
                DispatchQueue.main.async {
                    
                    if let error = error{
                        self?.hasError = true
                        self?.error = UserError.custom(error: error)
                    }
                    
                    else{
                        
                        let decoder = JSONDecoder()
                        //decoder.keyDecodingStrategy = .useDefaultKeys
                        
                        if let data = data,
                            let genres = try? decoder.decode([Genre].self, from: data){
                            
                            self?.genres = genres
                        }
                        
                        else{
                            self?.hasError = true
                            self?.error = UserError.failedToDecode
                        }
                    }
                }
            }.resume()
        }
    }
}


extension GenreViewModel{
    
    enum UserError: LocalizedError{
        case custom(error: Error)
        case failedToDecode
        
        var errorDescription: String?{
            switch self{
            case .failedToDecode:
                return "Failed to decode"
            case .custom(let error):
                return error.localizedDescription
            }
            
            
            
        }
    }
}

'''

My GenreView Code '''

import SwiftUI

struct GenreView: View{
    let genre: Genre
    var body: some View{
        VStack(alignment: .leading){
            Text("**Genre**: \(genre.id)")
            Text("**Name**: \(genre.name)")
            Text("**Picture**: \(genre.picture)")
        }
    }
        
        
}


struct GenreView_Previews: PreviewProvider{
    
    static var previews: some View{
        GenreView(genre: .init(id: 0, name: "a", picture: "pic"))
    }
}

'''

My ContentView Code

'''

import SwiftUI

struct ContentView: View {
    
    @StateObject private var gVM = GenreViewModel()
    
    var body: some View {
        NavigationView{
            ZStack{
                List{
                    ForEach(gVM.genres, id: \.id){ genre in
                        GenreView(genre: genre).listStyle(.plain)
                    }
                }.listStyle(.plain).navigationTitle("Users")
                    .alert(isPresented: $gVM.hasError, error: gVM.error){
                        Button(action: gVM.fetchGenres){
                            Text("try again")
                        }
                    }
            }.onAppear(perform: gVM.fetchGenres)
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

'''


Solution

  • Try this example code, where the root ApiResponse is used to decode the json data you get from the server.

    struct ApiResponse: Codable {
        let data: [Genre]
    }
    
    struct Genre: Identifiable, Codable {
        let id: Int
        let name, picture, type: String
        let pictureSmall, pictureMedium, pictureBig, pictureXl: String
    
        enum CodingKeys: String, CodingKey {
            case id, name, picture, type
            case pictureSmall = "picture_small"
            case pictureMedium = "picture_medium"
            case pictureBig = "picture_big"
            case pictureXl = "picture_xl"
        }
    }
    
    final class GenreViewModel: ObservableObject {
        @Published var genres: [Genre] = []
        @Published var hasError = false
        @Published var error: UserError?
        
        func fetchGenres() {
            hasError = false
            let genreUrl = "https://api.deezer.com/genre"
            
            if let url = URL(string: genreUrl) {
                URLSession.shared.dataTask(with: url){ data,response, error in
                    DispatchQueue.main.async {
                        if let error = error {
                            self.hasError = true
                            self.error = UserError.custom(error: error)
                        } else {
                            if let data = data {
                                do {
                                    let decoder = JSONDecoder()
                                    let genres = try decoder.decode(ApiResponse.self, from: data)
                                    self.genres = genres.data
                                    return
                                } catch {
                                    print(error)
                                }
                            } 
                                self.hasError = true
                                self.error = UserError.failedToDecode
                        }
                    }
                }.resume()
            }
        }
    }