SwiftUI + 解码 Yelp API 响应

SwiftUI + Decoding Yelp API Response

我是 Swift 和 JSON 的新手,我会尽我所能描述我想要完成的工作。

我正在尝试访问 Yelp API 服务和 return 并解码 JSON 结果并在列表中显示结果。

我已经成功地能够点击 API 并将结果记录到控制台,但是我无法将结果映射到 UI 元素以在视图中显示.

下面是结果的结构和我试图在其中显示结果的视图。我return在视图加载后从我的加载函数中获取错误。

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
}

内容视图

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 来自 Yelp 的 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
        }
    }
}

首先,请注意在您的 JSON 响应的顶层有一个对象 { } 而不是数组 [ ].

这意味着你需要解码 BusinessesResponse 而不是 [RestaurantResponse]:

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

另请注意,您正在尝试解码 restaurants,并且在 JSON 响应中您有 businesses。您可以将 BusinessesResponse 中的字段重命名为 restaurants 或者,这可能 更好 ,使用 CodingKeys:

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

    let restaurants: [RestaurantResponse]
}

另请注意,JSON 响应中不存在 categoryimageUrl 字段。 JSON 响应中的 categories 字段是 数组 objects.

相反你可以这样做:

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
}

如果您决定使用 CodingKeys,那么您还可以更改其他变量:is_closedreview_countcamelCase.

或者,如果模型中的所有变量都是 camelCase 等效于 snake_case 中的键 JSON 您可以使用的响应:

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