Displaying JSON data

Thanks for the speedy reply!

With all the adjustments from your pointers so far this is the only error I’m getting:

Do you still have a type called Pickup declared somewhere?

Just so we are on the same page. Here is what I have:

ContentViewModel.swift

import Foundation

class ContentViewModel: ObservableObject {
    @Published var accept = [OrderAccepted]()

    init() {
        fetchRemoteData()
    }

    func fetchLocalData() {

        let jsonUrl = Bundle.main.url(forResource: "data", withExtension: "json")
        
        do {
            //  Read the file into a data object
            let jsonData = try Data(contentsOf: jsonUrl!)
            
            let decoder = JSONDecoder()
            do {
                let decodedData = try decoder.decode([OrderAccepted].self, from: jsonData)
                self.accept = decodedData
            } catch {
                print("Unable to decode JSON data \(error.localizedDescription)")
            }
        } catch {
            print("Unable to find JSON data")
        }
    }

    func fetchRemoteData() {
        let urlString = "https://www.goedash.com/_functions/api/accept" //http://d1058276.myweb.iinethosting.net.au/data.json
        let url = URL(string: urlString)
        let defaultSession = URLSession(configuration: .default)
        let dataTask = defaultSession.dataTask(with: url!) { data, response, error in

            if error != nil {
                print(error!)
                return
            }

            do {
                let json = try JSONDecoder().decode([OrderAccepted].self, from: data!)
                DispatchQueue.main.async {
                    self.accept = json
                }
            } catch {
                print(error)
            }
        }
        dataTask.resume()
    }
}

OrderAccepted.swift

import Foundation

struct OrderAccepted: Decodable {
    //although what kind of name is Accept?
    
    let pickup, dropoff: LocationInfo
    
    let orderDetails: [OrderDetail]

    let pickupTime: String
    let quoteExternalReference: String
    let deliveryExternalReference: String
    let tip: Double
    let deliveryID: String
    let statusUpdateURL: String
    let dropoffTime: String
    let controlledContents: String
    let allowedVehicles: String
    let orderValue: Double
    let brandName: String
    let currency: String

    enum CodingKeys: String, CodingKey {
        case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
        case deliveryID = "deliveryId"
        case statusUpdateURL = "statusUpdateUrl"
        case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
    }
}

struct LocationInfo: Decodable, Identifiable {
    
    let id: UUID
    let name: String
    let phoneNumber: String
    let street: String
    let city: String
    let state: String
    let postalCode: String
    let country: String
    let latitude: Double
    let longitude: Double
    let unit: String
    let instructions: String
}

struct OrderDetail: Decodable {
    let title: String
    let quantity: Int
}

data.json

{
  "pickup": {
    "id": "7ad33209-1223-449b-afbc-c69d20935389",
    "name": "Kermit's Salads",
    "phoneNumber": "+12125551234",
    "street": "26 Broadway",
    "city": "New York City",
    "state": "NY",
    "postalCode": "10004",
    "country": "US",
    "latitude": 40.716038,
    "longitude": -74.00631,
    "unit": "104B",
    "instructions": "Use back entrance"
  },
  "dropoff": {
    "id": "7ad33209-1223-449b-afbc-c69d20935389",
    "name": "Miss Piggy",
    "phoneNumber": "+12125555678",
    "street": "312 Broadway",
    "city": "New York City",
    "state": "NY",
    "postalCode": "10004",
    "country": "US",
    "latitude": 40.24377,
    "longitude": -74.10277,
    "unit": "Suite 300",
    "instructions": "Leave with security guard"
  },
  "orderDetails": [
    {
      "title": "Salad Green",
      "quantity": 3
    }
  ],
  "pickupTime": "2015-09-22T18:30:00.0000000Z",
  "quoteExternalReference": "basket_e699aece",
  "deliveryExternalReference": "order_713a8bd9",
  "tip": 3.5,
  "deliveryId": "cb1915b1-3330-4477-b4bf-88c9b935943c",
  "statusUpdateUrl": "https://mywebsite.com/v1",
  "dropoffTime": "2015-09-22T18:30:00.0000000Z",
  "controlledContents": "Alcohol,Tobacco",
  "allowedVehicles": "Walker,Bicycle,DeliveryBicycle,Car,Van",
  "orderValue": 22.5,
  "brandName": "BigBellyBurger",
  "currency": "USD"
}

Hmm, don’t see anything called Pickup there. Might be time to clean your build folder.

Cleaning totally worked! Just one more thing. How do I correct this:

contentView.swift

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var model: ContentViewModel

    var body: some View {
        List(model.accept) { pickup in
            
            Group {
                
            Text("\(pickup.id)")
            Text("\(pickup.name)")
            Text("\(pickup.phoneNumber)")
            Text("\(pickup.street)")
            Text("\(pickup.city)")
            Text("\(pickup.state)")
                
            }
            
            Text("\(pickup.postalCode)")
            Text("\(pickup.country)")
            Text("\(pickup.latitude)")
            Text("\(pickup.longitude)")
            Text("\(pickup.unit)")
            Text("\(pickup.instructions)")
            
        }
    }
}

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

You need to give OrderAccepted an id property so that SwiftUI has a way to distinguish between different orders.

Probably the easiest way is to just add this:

var id: String { deliveryID }

This uses a computed property to simply pass through the deliveryID property decoded from the JSON response. You can use another property if you prefer, though.

1 Like

This is a puzzle. How would I implement:

var id: String { deliveryID }
import Foundation

struct OrderAccepted: Decodable, Identifiable {
    
    var id: String { deliveryID }
    
    var pickup, dropoff: LocationInfo
    
    let orderDetails: [OrderDetail]

    let pickupTime: String
    let quoteExternalReference: String
    let deliveryExternalReference: String
    let tip: Double
    let deliveryID: String
    let statusUpdateURL: String
    let dropoffTime: String
    let controlledContents: String
    let allowedVehicles: String
    let orderValue: Double
    let brandName: String
    let currency: String

    enum CodingKeys: String, CodingKey {
        case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
        case deliveryID = "deliveryId"
        case statusUpdateURL = "statusUpdateUrl"
        case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
    }
}

struct LocationInfo: Decodable, Identifiable {
    
    let id: UUID
    let name: String
    let phoneNumber: String
    let street: String
    let city: String
    let state: String
    let postalCode: String
    let country: String
    let latitude: Double
    let longitude: Double
    let unit: String
    let instructions: String
}

struct OrderDetail: Decodable {
    let title: String
    let quantity: Int
}


…in my contentView:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var model: ContentViewModel

    var body: some View {
        List(model.accept) { locationinfo in
            
            Group {
                
            Text("\(locationinfo.id)")
            Text("\(locationinfo.name)")
            Text("\(locationinfo.phoneNumber)")
            Text("\(locationinfo.street)")
            Text("\(locationinfo.city)")
            Text("\(locationinfo.state)")
                
            }
            
            Text("\(locationinfo.postalCode)")
            Text("\(locationinfo.country)")
            Text("\(locationinfo.latitude)")
            Text("\(locationinfo.longitude)")
            Text("\(locationinfo.unit)")
            Text("\(locationinfo.instructions)")
            
        }
    }
}

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

You don’t need to do anything in ContentView. Making OrderAccepted conform to the Identifiable protocol satisfies the requirement on ForEach that was causing the error message.

Unless you are getting other errors you should be good to go.

Although, hang on… right after hitting Reply I noticed that you are expecting to receive a LocationInfo struct in the ForEach but you will not. Your ContentViewModel.accept property is an array of OrderAccepted structs, each of which has a pickup and dropoff property. Those properties are LocationInfos. So you need to correctly drill down to get them from the OrderAccepted items you are being given by the ForEach.

I’m away from home on my iPad right now so I can’t give you a code example but I will work something up tonight to illustrate if you haven’t got it working by then.

1 Like

Hello, I am revisiting this problem that I didn’t get solved. I keep getting this error on this image blow

ContentView.swift

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var model: ContentViewModel

    var body: some View {
        List(model.order) { pickup in
           
            Text("\(pickup.name )")
            
            
        }
    }
}

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

ContentViewModel.swift

mport Foundation

class ContentViewModel: ObservableObject {
    @Published var order = [Order]()

    init() {
        fetchRemoteData()
    }

    func fetchLocalData() {

        let jsonUrl = Bundle.main.url(forResource: "data", withExtension: "json")
        
        do {
            //  Read the file into a data object
            let jsonData = try Data(contentsOf: jsonUrl!)
            
            let decoder = JSONDecoder()
            do {
                let decodedData = try decoder.decode([Order].self, from: jsonData)
                self.order = decodedData
            } catch {
                print("Unable to decode JSON data \(error.localizedDescription)")
            }
        } catch {
            print("Unable to find JSON data")
        }
    }

    func fetchRemoteData() {
        let urlString = "https://www.goedash.com/_functions/api/accept" //http://d1058276.myweb.iinethosting.net.au/data.json
        let url = URL(string: urlString)
        let defaultSession = URLSession(configuration: .default)
        let dataTask = defaultSession.dataTask(with: url!) { data, response, error in

            if error != nil {
                print(error!)
                return
            }

            do {
                let json = try JSONDecoder().decode([Order].self, from: data!)
                DispatchQueue.main.async {
                    self.order = json
                }
            } catch {
                print(error)
            }
        }
        dataTask.resume()
    }
}

Order.swift

import Foundation

struct Order: Codable {
    //although what kind of name is Accept?
    
    let pickup, dropoff: LocationInfo
    
    let orderDetails: [OrderDetail]

    let pickupTime: String
    let quoteExternalReference: String
    let deliveryExternalReference: String
    let tip: Double
    let deliveryID: String
    let statusUpdateURL: String
    let dropoffTime: String
    let controlledContents: String
    let allowedVehicles: String
    let orderValue: Double
    let brandName: String
    let currency: String

    enum CodingKeys: String, CodingKey {
        case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
        case deliveryID = "deliveryId"
        case statusUpdateURL = "statusUpdateUrl"
        case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
    }
}

struct LocationInfo: Codable {
    let name: String
    let phoneNumber: String
    let street: String
    let city: String
    let state: String
    let postalCode: String
    let country: String
    let latitude: Double
    let longitude: Double
    let unit: String
    let instructions: String
}

struct OrderDetail: Codable {
    let title: String
    let quantity: Int
}

data.json

{
  "pickup": {
    "id": "7ad33209-1223-449b-afbc-c69d20935389",
    "name": "Kermit's Salads",
    "phoneNumber": "+12125551234",
    "street": "26 Broadway",
    "city": "New York City",
    "state": "NY",
    "postalCode": "10004",
    "country": "US",
    "latitude": 40.716038,
    "longitude": -74.00631,
    "unit": "104B",
    "instructions": "Use back entrance"
  },
  "dropoff": {
    "id": "7ad33209-1223-449b-afbc-c69d20935389",
    "name": "Miss Piggy",
    "phoneNumber": "+12125555678",
    "street": "312 Broadway",
    "city": "New York City",
    "state": "NY",
    "postalCode": "10004",
    "country": "US",
    "latitude": 40.24377,
    "longitude": -74.10277,
    "unit": "Suite 300",
    "instructions": "Leave with security guard"
  },
  "orderDetails": [
    {
      "title": "Salad Green",
      "quantity": 3
    }
  ],
  "pickupTime": "2015-09-22T18:30:00.0000000Z",
  "quoteExternalReference": "basket_e699aece",
  "deliveryExternalReference": "order_713a8bd9",
  "tip": 3.5,
  "deliveryId": "cb1915b1-3330-4477-b4bf-88c9b935943c",
  "statusUpdateUrl": "https://dispatch.goedash.com/v1",
  "dropoffTime": "2015-09-22T18:30:00.0000000Z",
  "controlledContents": "Alcohol,Tobacco",
  "allowedVehicles": "Walker,Bicycle,DeliveryBicycle,Car,Van",
  "orderValue": 22.5,
  "brandName": "BigBellyBurger",
  "currency": "USD"
}

DataCollectionApp.swift

import SwiftUI

@main
struct DataCollectionApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(ContentViewModel())
        }
    }
}

What’s the disconnect and how do I fix it?

The error message is telling you that your Order type needs to conform to the Identifiable protocol. This is common in SwiftUI, as it’s how the system can tell that items are unique and distinct from one another when diffing the View to see if rerendering is needed.

To make a type conform to Identifiable, you need to provide an id property that is guaranteed to be unique across all instances of the type, i.e., all Orders in your case.

So find some property that is unique to each Order and assign that as the id property. Take a look at the JSON you are working with. Is there something that uniquely identifies each Order? Soemthing like… deliveryID, perhaps?

You have two choices here:

  1. Rename the deliveryID property to just id. This would also require that you adjust the CodingKeys enum like below and change anywhere else you use deliveryID to use id instead:
    enum CodingKeys: String, CodingKey {
        case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
        case id = "deliveryId"
        case statusUpdateURL = "statusUpdateUrl"
        case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
    }

2.Provide a separate id computed property in Order that just passes through the existing deliveryID property:

var id: String { deliveryID }

Either way, you will also need to tell the compiler that Order conforms to Identifiable, like this:

struct Order: Identifiable, Codable {
//...blah blah blah...
}

Which option you choose depends what you are doing in the rest of your code and how you are using the existing deliveryID property.

In trying to solve the error I ran into another error. What can satisfy the from:___?

import Foundation

class ContentViewModel: ObservableObject {
@Published var order = Order(from: )

init() {
    fetchRemoteData()
}

func fetchLocalData() {

    let jsonUrl = Bundle.main.url(forResource: "data", withExtension: "json")
    
    do {
        //  Read the file into a data object
        let jsonData = try Data(contentsOf: jsonUrl!)
        
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(Order.self, from: jsonData)
            self.order = decodedData
        } catch {
            print("Unable to decode JSON data \(error.localizedDescription)")
        }
    } catch {
        print("Unable to find JSON data")
    }
}

func fetchRemoteData() {
    let urlString = "https://www.goedash.com/_functions/api/accept" //http://d1058276.myweb.iinethosting.net.au/data.json
    let url = URL(string: urlString)
    let defaultSession = URLSession(configuration: .default)
    let dataTask = defaultSession.dataTask(with: url!) { data, response, error in

        if error != nil {
            print(error!)
            return
        }

        do {
            let json = try JSONDecoder().decode(Order.self, from: data!)
            DispatchQueue.main.async {
                self.order = json
            }
        } catch {
            print(error)
        }
    }
    dataTask.resume()
}

}

That error in the debugger is not related to the error message Xcode is showing you on line 12.

The one in the debugger is a runtime error that shows when some JSON you are trying to decode doesn’t match the format you say it should have. It’s probably left over from a previous run.

The error Xcode is showing you on line 12 is a compiler error you get because you are trying to call an initializer for Order that does not exist. At least in the example code you previously posted, there is no method called init(from:).

I’m a little confused what you are trying to do now. Previously, your ContentViewModel contained a published array of Order items, now you just have a single Order.

In order to do that, you need to either implement an init method that creates an empty Order itemm or make the order property an Optional and default it to nil.

I see the confusion. There was a tutorial that was helping me solve the error problem in the debugger that basically says that Im trying to connect within an array, but I have a dictionary. This…

@Published var order = Order(from: )

…was originally this:

@Published var order = [Order]()

and this…

 let decodedData = try decoder.decode(Order.self, from: jsonData)

was originally this:

 let decodedData = try decoder.decode([Order].self, from: jsonData)

You are right that it wasn’t matching up. I was wondering trying to find the missing piece of the puzzle.