jsonswiftdatabaseswiftuinetworkmanager

Network manager in Swift


i am trying to create a CRM app, for this i created a database on my raspberry Pi, and used PHP to create JSON results on a url.

Now i have created the following Network manager in swift, but the invalidData error at the end in the catch keeps getting thrown. What am i doing wrong ?

import Foundation

final class NetworkManager {
    static let shared = NetworkManager()
    static let baseURL = ""
    private let buyersURL = baseURL + ""
    
    private init(){}
    
    func getBuyers(completed: @escaping (Result<[Buyer], CRMError>) -> Void) {
        guard let url = URL(string: buyersURL) else {
            completed(.failure(.invalidURL))
            return
        }
        let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, response, error in
            if let _ = error {
                completed(.failure(.unableToComplete))
                return
            }
            
            guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                completed(.failure(.invalidResponse))
                return
            }
            
            guard let data = data else {
                completed(.failure(.invalidData))
                return
            }
            
            do {
                let decoder = JSONDecoder()
                let decodedResponse = try decoder.decode(BuyerResponse.self, from: data)
                completed(.success(decodedResponse.request))
            }  catch {
                completed(.failure(.invalidData))
            }
        }
        task.resume()
    }
}

The following is the result of the JSON on the URL

JSON result

and the following is my Buyer struct:

import Foundation

struct Buyer: Codable, Identifiable, Hashable{
    let id: Int
    let user_id: Int
    let name: String
    let email: String
    let country: String
    let phone: String
    let second_phone: String
    let whishes: String
    let budget: String
    let timing: String
    let fase: String
    let media: String
    let plans: String
    let extra: String
    let hot: Int
}

struct BuyerResponse : Codable {
    let request: [Buyer]
}

And this is the view in which i call get the buyers:

struct BuyersView: View {
    @StateObject var viewModel = BuyersViewModel()

    @State var path = NavigationPath()
    
    @State private var buyers: [Buyer] = []
    
    var body: some View {
        NavigationStack(path: $path) {
            VStack{
                CategoryView(categories: viewModel.fases, currentSelection: $viewModel.currentFase)
                HStack{
                    Spacer()
                    NavigationLink("Add buyer", value: 0)
                        .padding()
                }
                List(buyers){ buyer in
                    BuyerCellView(buyer: buyer)
                }
            }
//            .navigationDestination(for: Int.self) { _ in
//                AddBuyerView(path: $path, fases: viewModel.fases, medias: $viewModel.media)
//            }.navigationDestination(for: Buyer.self) { buyer in
//                BuyerDetailView(buyer: buyer)
//            }
        }.onAppear{
             getBuyers()
        }
    }
    
    func getBuyers() {
        NetworkManager.shared.getBuyers { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let buyers):
                    self.buyers = buyers
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }
}

I checked that the variables on the JSON and the struct of Buyer are the same, but the error keeps happening, i just expect that the variable buyers returns a list of Buyers from the JSON.

I folowed the tutorial of https://www.youtube.com/watch?v=b1oC7sLIgpI&t=22362s at around 6 hours into the video

Thank you for helping in advance


Solution

  • The inside of BuyerResponse you say that the JSON should look like this:

    { request: [{buyer 1 properties}, {buyer 2 properties}] }
    

    But judging from your screenshot, the response looks like this:

        [{buyer 1 properties}, {buyer 2 properties}]
    

    Inside of your PHP script you should adjust the response so that you get that request: before the list starts. That should do the trick.

    Another way would be to change that line here

    let decodedResponse = try decoder.decode(BuyerResponse.self, from: data)
    

    to

    let decodedResponse = try decoder.decode([Buyer].self, from: data)
    

    That way you tell the decoder that it's getting a list of Buyers without anything around it.

    Generally if you get an error at that position, it means that the data that you receive does not match the data structure that you specify.

    A good way to approach this is to just create a model struct with only one property and check whether that works. Then add another property, etc. If it doesn't work: print the error you receive and that will tell you at what line of the JSON there is something unexpected.