[SOLVED] parsing JSON response and displaying on swiftui screen

Issue

everyone. in my project, for some reason i can’t display json data. in general, I don’t know how to extract and use the data inside the json object ?!

these are my attempts 📌
Thanks in advance for the answers and attention !!

[this is the api I need to display][👇]

http://dummy.restapiexample.com/#:~:text=http%3A//dummy.restapiexample.com/api/v1/employees

{
"status": "success",
"data": [
    {
     "id": "1",
     "employee_name": "Tiger Nixon",
     "employee_salary": "320800",
     "employee_age": "61",
     "profile_image": ""
    },
    {
      "id": 2,
      "employee_name": "Garrett Winters",
      "employee_salary": 170750,
      "employee_age": 63,
      "profile_image": ""
        },
        {
      "id": 3,
      "employee_name": "Ashton Cox",
      "employee_salary": 86000,
      "employee_age": 66,
      "profile_image": ""
        },
        {
       "id": 4,
       "employee_name": "Cedric Kelly",
       "employee_salary": 433060,
       "employee_age": 22,
       "profile_image": ""
        },
      ...
    ]
}

(1👇) this is my model: Employee

import Foundation

struct Employee: Decodable {
    var success: String?
    var data:  [Details]?
    var message: String?


    struct Details: Decodable {
    var id: Int?
    var employee_name: String?
    var employee_salary: Int?
    var employee_age: Int?
    var profile_image: String?
    }
}

(2 👇) used AF(alamofire)

import Foundation
import Alamofire

private let IS_TESTER = true
private let DEP_SER = "https://dummy.restapiexample.com/"
private let DEV_SER = "https://dummy.restapiexample.com/"

let headers: HTTPHeaders = [
    "Accept": "application/json"
]

//CustomClass
class AF_Talking {
    
    //MARK: AF_Talking Requests 
    class func get(url: String, params: Parameters, handler: @escaping (AFDataResponse<Any>) -> Void) {
        AF.request(server(url: url), method: .get, parameters: params, headers: headers).validate().responseJSON(completionHandler: handler)
    }
    
    
    //MARK: AF_Talking Methods
    class func server(url: String) -> URL {
        if(IS_TESTER) {
            return URL(string: DEV_SER + url)!
        } else {
            return URL(string: DEP_SER + url)!
        }
    }
    
    //MARK: AF_Talking APIs
    static let API_EMPLOYEES_LIST = "api/v1/employees"  // list
    
    //MARK: AF_Talking APIs

    class func paramsEmpty() -> Parameters {
        let parameters: Parameters = [
            :]  // <- 🛑 shouldn't there be some change here?
        return parameters
    }
}

(3👇) fetching

import Foundation

class EmployeeView: ObservableObject {
    
    @Published var employees = Employee().data //🛑 of course, is an error
    @Published var isLoading = false
    
    func apiEmployeesList() {
        isLoading = true
        AF_Talking.get(url: AF_Talking.API_EMPLOYEES_LIST, params: AF_Talking.paramsEmpty(), handler: { response in
            self.isLoading = false
            switch response.result {
            case .success:
                print(response.result)
                let employees = try! JSONDecoder().decode(Employee().data.self, from: response.data!)
                self.employees = employees
            case let .failure(error):
                print(error)
            }
        })
    }
}

(4👇) MainView

import SwiftUI
import Alamofire

struct MainView: View {
    
    @ObservedObject var viewModel = EmployeeView()
    
    var body: some View {
        NavigationView {
            ZStack {
                List(viewModel.employees.data!, id: \.id) { employee in // 🛑
                    VStack {
                        Text(employee.employee_name!.uppercased()).fontWeight(.bold)
                        Text(employee.employee_salary!).padding(.top)
//                        Text(employee.employee_age!).padding(.top)
                    }
                }
                if viewModel.isLoading {
                    ProgressView()
                }
            }
            .navigationBarItems(trailing: NavigationLink(destination: DetailView(), label: {    Text("Next Page")   }))
        }.onAppear {
            viewModel.apiEmployeesList()
        }
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}

problem: take the nested json obj. and display on simulator

Solution

First of all name the root struct Response and the employees Employee.

Second of all if the API sends all fields declare the properties non-optional.

Third of all if the properties won’t be modified declare them as constants (let).

Further add CodingKeys and adopt Identifiable because id already exists.


There is an error in the root struct. There is no key success, but there is a key status with value success

struct Response: Decodable {
    let status: String
    let data:  [Employee]
    let message: String


    struct Employee: Decodable, Identifiable {
        let id: Int
        let name: String
        let salary: Int
        let age: Int
        let profileImage: String
        
        private enum CodingKeys: String, CodingKey {
            case id
            case name = "employee_name"
            case salary = "employee_salary"
            case age = "employee_age"
            case profileImage = "profile_image"
        }
    }
}

The AF part is too complicated. Most of the code is not needed. The Talking class is actually not needed at all. And the naming is confusing again. Name it EmployeeModel

class EmployeeModel: ObservableObject {

    @Published var employees = [Response.Employee]()
    @Published var isLoading = false

    func apiEmployeesList() {
        let url = URL(string: "http://dummy.restapiexample.com/api/v1/employees")!
        isLoading = true
        AF.request(url).validate().responseDecodable(of: Response.self, decoder: JSONDecoder()) { response in
            self.isLoading = false
            switch response.result {
                case .success(let result):
                    print(result)
                    self.employees = result.data
                case .failure(let error):
                    print(error)
            }
        }
    }
}

The view is almost right.

import SwiftUI

struct MainView: View {
    
    @StateObject var viewModel = EmployeeModel()
    
    var body: some View {
        NavigationView {
            ZStack {
                List(viewModel.employees) { employee in                        
                    VStack(alignment: .leading, spacing: 10) {
                        Text(employee.name.uppercased()).bold()
                        Text(String(employee.salary))
                        Text(String(employee.age))
                    }
                }
                if viewModel.isLoading {
                    ProgressView()
                }
            }
            .navigationBarItems(trailing: NavigationLink(destination: DetailView(), label: {    Text("Next Page")   }))
        }.onAppear {
            viewModel.apiEmployeesList()
        }
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}

Answered By – vadian

Answer Checked By – David Marino (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *