0

I am creating a new version of an existing app and want to use the new async await format for a web request. Placing a break at the JSONDecoder().decode line I see that I do have data - but the decoding does not work. (The url and my key DO work in the old version)

Here's the JSON format of the web source (shortened - there are many more items in a fuel_station):

{
   "station_locator_url":"https://afdc.energy.gov/stations/",
   "total_results":110,
   "station_counts":{},
   "fuel_stations":[
      {
         "access_code":"public",
         "access_days_time":"24 hours daily; call 866-809-4869 for Clean Energy card",
         "access_detail_code":"KEY_ALWAYS",
         "cards_accepted":"CleanEnergy",
         "date_last_confirmed":"2021-09-10",
      }
   ]
}

I created the following models from the above:

enum CodingKeys: String, CodingKey {
    case fuelStations = "fuel_stations"
    case accessCode = "access_code"
    case accessDaysTime = "access_days_time"
    case accessDetailCode = "access_detail_code"
    case cardsAccepted = "cards_accepted"
    case dateLastConfirmed = "date_last_confirmed"
}

struct TopLevel: Codable {
    let fuelStations: [FuelStation]
}

struct FuelStation: Codable {
    let accessCode, accessDaysTime, accessDetailCode, cardsAccepted: String
    let dateLastConfirmed: String
    let id: String
}

I put a simplified version of the initial view in one file for testing:

struct SiteListView: View {
    @State private var fuelStations: [FuelStation] = []
    @State private var topLevel: TopLevel = TopLevel(fuelStations: [])

    var body: some View {
        NavigationView {
            VStack {
                List(fuelStations, id: \.id) { item in
                    VStack {
                        Text(item.accessCode)
                        Text(item.accessDaysTime)
                    }
                }
            }
            .navigationTitle("Site List View")
            .task {
                await loadData()
            }
        }//nav
    }

    func loadData() async {
    
    //I believe the DEMO_KEY in the url will allow limited retrievals
        guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY") else {
            print("Invalid URL")
            return
        }

        do {
            let (data, response) = try await URLSession.shared.data(from: url)
            guard (response as? HTTPURLResponse)?.statusCode == 200 else { return }
            print("response status code is 200")
        
            if let decodedResponse = try? JSONDecoder().decode(TopLevel.self, from: data) {
                topLevel = decodedResponse
                print("in decoding: topLevel.fuelStations.count is \(topLevel.fuelStations.count)")
                //I would iterate through topLevel here and add to the fuelStations 
                //array but I never get here
            }
        } catch {
            print("Invalid Data")
        }

    }//load data
}//struct

Any guidance would be appreciated. Xcode 13.2.1 iOS 15.2

2
  • Why do you ignore the error which tells you at once what's wrong? Hint: Your CodingKeys are lost. Commented Jan 16, 2022 at 21:36
  • Understood. Thanks. Commented Jan 16, 2022 at 22:29

1 Answer 1

1

First you should remove ? from try? for the catch to work when there is a problem in decoding like this

func loadData()  async {
   //I believe the DEMO_KEY in the url will allow limited retrievals
   guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY") else {
       print("Invalid URL")
       return
   }

   do {
       let (data, response) = try await URLSession.shared.data(from: url)
       guard (response as? HTTPURLResponse)?.statusCode == 200 else { return }
       print("response status code is 200") 
       let decoder = JSONDecoder()
       decoder.keyDecodingStrategy = .convertFromSnakeCase
       let decodedResponse = try decoder.decode(TopLevel.self, from: data) 
       print("in decoding: topLevel.fuelStations.count is \(decodedResponse.fuelStations.count)")
       //I would iterate through topLevel here and add to the fuelStations
       //array but I never get here
       
   } catch {
       print(error)
   } 
}

After you do this , you'll find that some attributes in your struct are coming null in response so you should change string to string? to finally be

struct TopLevel: Codable {
    let fuelStations: [FuelStation]
}

struct FuelStation: Codable {
    let accessCode, accessDaysTime, accessDetailCode, cardsAccepted,dateLastConfirmed: String?
    let id: Int
}

In addition note use of

   let decoder = JSONDecoder()
   decoder.keyDecodingStrategy = .convertFromSnakeCase

instead of hard-coding the enum

Sign up to request clarification or add additional context in comments.

1 Comment

Understood. Your changes work. I'll need to re-read the decoding keys documentation. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.