Home > Software design >  API call only works for JSON files with an ID
API call only works for JSON files with an ID

Time:07-04

I looked at the other StackOverFlow answers but none of them worked in allowing me to make an API call using the Codable struct as shown below:

struct Response: Codable {
    var value: [Result]
}

struct Result: Identifiable, Codable {
    let id = UUID()
    var BusStopCode: String!
    var RoadName: String!
}
struct ContentView: View {
    @State private var value = [Result]()
    //var locations = [Locations]()
    var body: some View {
        List(value, id: \.id) { item in
            VStack(alignment: .leading) {
                Text(item.BusStopCode)
                    .font(.headline)
                Text(item.RoadName)
            }
        }
        .task {
            await loadData()
        }
        
    }
    
    func loadData() async {
        guard let url = URL(string: "google.com") //link replaced due to privacy reasons
        else { print("Invalid URL")
            return
        }
        
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            
            if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
                value = decodedResponse.value
            }
        } catch {
            print("Invalid data")
        }
        
    }
}

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

I used Postman to see the output and it's like this:

{
    "odata.metadata": "",
    "value": [
        {
            "BusStopCode": "01012",
            "RoadName": "Victoria St",
            "Description": "Hotel Grand Pacific",
            "Latitude": 1.29684825487647,
            "Longitude": 103.85253591654006
        },
        
        ...

There is no ID, so i used let id = UUID() and added an Identifiable property, but it doesn't seem to work as I have nothing shown on the Simulator, however when I use another data set (from iTunes) with an identifiable ID, it yields a list when the code is run.

CodePudding user response:

your struct for Result is correct with the let id = UUID(), it decodes the json data you provided without errors. I suspect the "response" from the server is not what you think (for example some error message). Try using

 struct Result: Identifiable, Codable {
     let id = UUID()
     var BusStopCode: String?  // <-- here
     var RoadName: String?     // <-- here
 }

You can also use the CodingKeys as mentioned, if you get really scared about the Xcode message.

Could you add print(String(data: data, encoding: .utf8)) just after let (data, _) = try await URLSession.shared.data(from: url), and show us exactly what it prints.

This is the full code I used to test my answer and show that it works:

struct Response: Codable {
    var value: [Result]
}

struct Result: Identifiable, Codable {
    let id = UUID()
    var BusStopCode: String?
    var RoadName: String?
}

struct ContentView: View {
    @State private var value = [Result]()
    //var locations = [Locations]()
    var body: some View {
        List(value, id: \.id) { item in
            VStack(alignment: .leading) {
                Text(item.BusStopCode ?? "no BusStopCode")
                    .font(.headline)
                Text(item.RoadName ?? "no RoadName")
            }
        }
        .task {
            await loadData()
        }
        
    }
    
    func loadData() async {
        let json = """
{
    "odata.metadata": "",
    "value": [
        {
            "BusStopCode": "01012",
            "RoadName": "Victoria St",
            "Description": "Hotel Grand Pacific",
            "Latitude": 1.29684825487647,
            "Longitude": 103.85253591654006
        }
]
}
"""
        guard let url = URL(string: "google.com") //link replaced due to privacy reasons
        else { print("Invalid URL")
            return
        }
        do {
           // let (data, _) = try await URLSession.shared.data(from: url)
            // simulated server response, since the url is not provided
            let data = json.data(using: .utf8)!
            if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
                value = decodedResponse.value
            }
        } catch {
            print("Invalid data: \(error)")
        }
    }
}

PS: your url should be https: as per Apple requirements, not http: unless you have set the appropriate NSAppTransportSecurity in your Info.plist

CodePudding user response:

Two ways to solve this without needing to add an extra property.

Identifiable

If you have an existing property, like perhaps BusStopCode, that is unique for each object then you can use that as your id property.

Add an extension to conform to Identifiable

extension Result: Identifiable {
    var id: String {
        BusStopCode
    }
}

Hashable

Another option is to conform to Hashable if each object as a whole (the combination of the properties) is unique. I assume this should also work fine with your data.

Simply conform to Hashable and the compiler does the rest.

extension Result: Hashable {}

You do have to change your List though so you use self as id

List(value, id: \.self) { item in

Apart from that I do think you should follow swifts naming standard and start property names with a lowercase letter and you should definitely not use forced unwrapped properties.

struct Result: Codable {
    var busStopCode: String
    var roadName: String

    enum CodingKeys: String, CodingKey {
        case busStopCode = "BusStopCode"
        case roadName = "RoadName"
    }
}
  • Related