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)