Jason Tremain
Jason Tremain

Reputation: 1399

SwiftUI + Decoding Yelp API Response

I'm new to working with Swift and JSON, I'll try to describe what I'm trying to accomplish as best as I can.

I'm trying to access the Yelp API service and return and decode the JSON results and display the results in a list.

I've successfully been able to hit the API and log the results to the console, but I haven't been able to map the results to UI elements to display within a view.

Below is the struct for the results and the view I'm attempting to display the results in. I'm returning the error from my load function after the view loads.

Data.swift

import SwiftUI

struct BusinessesResponse: Codable {
    let restaurants: [RestaurantResponse]
}

struct RestaurantResponse: Codable, Identifiable {
    let id: String
    var name: String
    var coordinates: [longlat]
    var is_closed: Bool
    var category: String
    var imageURL: URL
    var url: URL
    var review_count: Int
   var rating: Double
   var display_phone: String
   var distance: Double
}

ContentView

import SwiftUI
import YelpAPI
import Combine
import CoreLocation

struct ContentView: View {

    @ObservedObject private var locationManager = LocationManager()
    @ObservedObject var fetcher = RestaurantFetcher()
    
    var body: some View {
        VStack {
            List(fetcher.businesses) { restaurant in
                VStack (alignment: .leading) {
                    Text(restaurant.name)
                }
              }
           }
        }
    }

    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
    }
}
public class RestaurantFetcher: ObservableObject {
    @Published var businesses = [RestaurantResponse]()
    
    init() {
        load(latitude: 28.4293403, longitude: -81.6241764)
    }
    
    func load(latitude: Double, longitude: Double) {
         let apikey = "API-KEY-HERE"
        let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)")!
        var request = URLRequest(url: url)
        request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"
        
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            do {
                if let d = data {
                    let decodedLists = try JSONDecoder().decode([RestaurantResponse].self, from: d)
                    DispatchQueue.main.async {
                        self.businesses = decodedLists
                    }
                } else {
                    print("No Data")
                }
            } catch {
                print ("Caught")
            }
        }.resume()
    }
}

JSON response from Yelp's API

{
    "businesses": [
        {
            "id": "ZTgp2l3XbADwmOMM5rpWZg",
            "alias": "disneys-oak-trail-golf-course-lake-buena-vista",
            "name": "Disney's Oak Trail Golf Course",
            "image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/G3oE_KJJ53H1iweD-j83yQ/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/disneys-oak-trail-golf-course-lake-buena-vista?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
            "review_count": 12,
            "categories": [
                {
                    "alias": "golf",
                    "title": "Golf"
                }
            ],
            "rating": 3.5,
            "coordinates": {
                "latitude": 28.4055855,
                "longitude": -81.5956011
            },
            "transactions": [],
            "location": {
                "address1": "1950 W Magnolia Palm Dr",
                "address2": "",
                "address3": "",
                "city": "Lake Buena Vista",
                "zip_code": "32836",
                "country": "US",
                "state": "FL",
                "display_address": [
                    "1950 W Magnolia Palm Dr",
                    "Lake Buena Vista, FL 32836"
                ]
            },
            "phone": "+14079394653",
            "display_phone": "(407) 939-4653",
            "distance": 3845.3340908128034
        },
        {
            "id": "VVF9h1jhhOVXIvxe-MDK8g",
            "alias": "panther-lake-golf-course-winter-garden",
            "name": "Panther Lake Golf Course",
            "image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/ff47f9jXs56s3Cf7obIapA/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/panther-lake-golf-course-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
            "review_count": 1,
            "categories": [
                {
                    "alias": "hotels",
                    "title": "Hotels"
                },
                {
                    "alias": "golf",
                    "title": "Golf"
                }
            ],
            "rating": 4.0,
            "coordinates": {
                "latitude": 28.4419223,
                "longitude": -81.6303836
            },
            "transactions": [],
            "location": {
                "address1": "16301 Phil Ritson Way",
                "address2": "",
                "address3": "",
                "city": "Winter Garden",
                "zip_code": "34787",
                "country": "US",
                "state": "FL",
                "display_address": [
                    "16301 Phil Ritson Way",
                    "Winter Garden, FL 34787"
                ]
            },
            "phone": "+14076562626",
            "display_phone": "(407) 656-2626",
            "distance": 1620.1533458028462
        },
        {
            "id": "UdqKnhBDg4b04e38qFcjEA",
            "alias": "orange-83-pub-and-grill-winter-garden",
            "name": "Orange 83 Pub And Grill",
            "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/KjWvn26iBv13GnIUCW7z9Q/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/orange-83-pub-and-grill-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
            "review_count": 1,
            "categories": [
                {
                    "alias": "pubs",
                    "title": "Pubs"
                }
            ],
            "rating": 4.0,
            "coordinates": {
                "latitude": 28.4419223,
                "longitude": -81.6303836
            },
            "transactions": [],
            "location": {
                "address1": "16301 Phil Ritson Way",
                "address2": null,
                "address3": "Orange County National Golf Center & Lodge",
                "city": "Winter Garden",
                "zip_code": "34787",
                "country": "US",
                "state": "FL",
                "display_address": [
                    "16301 Phil Ritson Way",
                    "Orange County National Golf Center & Lodge",
                    "Winter Garden, FL 34787"
                ]
            },
            "phone": "+14076562626",
            "display_phone": "(407) 656-2626",
            "distance": 1620.1533458028462
        }
    ],
    "total": 3,
    "region": {
        "center": {
            "longitude": -81.6241764,
            "latitude": 28.4293403
        }
    }
}

Upvotes: 1

Views: 542

Answers (1)

pawello2222
pawello2222

Reputation: 54611

First of all, note that at the top level of your JSON response there is an object { } and not an array [ ].

Which means you need to decode BusinessesResponse instead of [RestaurantResponse]:

let response = try JSONDecoder().decode(BusinessesResponse.self, from: d)
self.businesses = response.restaurants

Also note you're trying to decode restaurants and in the JSON response you have businesses. You can either rename your field inside BusinessesResponse to restaurants or, which might be better, use CodingKeys:

struct BusinessesResponse: Codable {
    enum CodingKeys: String, CodingKey {
        case restaurants = "businesses"
    }

    let restaurants: [RestaurantResponse]
}

Also note you have category and imageUrl fields which don't exist in the JSON response. And the categories field in the JSON response is an array of objects.

Instead you can do:

struct RestaurantResponse: Codable, Identifiable {
    enum CodingKeys: String, CodingKey {
        case id, name, is_closed, categories, url, review_count, rating, display_phone, distance
        case imageURL = "image_url"
    }

    ...
    var categories: [RestaurantCategory]
    var imageURL: URL
    ...
}

struct RestaurantCategory: Codable {
    var alias: String
    var title: String
}

If you decide to use CodingKeys then you can also change your other variables: is_closed, review_count to camelCase.

Alternatively if all your variables in the model will be the camelCase equivalents of the snake_case keys in the JSON response you can use:

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

Upvotes: 1

Related Questions