How to parse array data from Yelp API? - arrays

Allo, Hi, im new with SwiftUI and im facing some issue with the Yelp API and can't find answer anywhere... I'm creating a restaurant app and I want to add a on the business detail page a list of Yelp transactions that the business is registered for ("pickup", "delivery", and "restaurant_reservation").
I've try a lot of way to retrieved it but im about to give up... I don't know if it's me who's dumb or anything but my brain can't figured it out anymore. I've tried to get the data with "ForEach" and all any other way we usually get array data...
Second question (similar as the previous one) how can I retrieve the category alias/title from the API? I want to be able to filter the business based on their categories and also show on the business detail page the category associated to it.
Thank you :)
Yelp Response Body Example :
{
"total": 144,
"businesses": [
{
"id": "gR9DTbKCvezQlqvD7_FzPw",
"alias": "north-india-restaurant-san-francisco",
"price": "$$",
"url": "https://www.yelp.com/biz/north-india-restaurant-san-francisco",
"rating": 4,
"location": {
"zip_code": "94105",
"state": "CA",
"country": "US",
"city": "San Francisco",
"address2": "",
"address3": "",
"address1": "123 Second St"
},
"categories": [
{
"alias": "indpak",
"title": "Indian"
}
],
"phone": "+14153481234",
"coordinates": {
"longitude": -122.399305736113,
"latitude": 37.787789124691
},
"image_url": "http://s3-media4.fl.yelpcdn.com/bphoto/howYvOKNPXU9A5KUahEXLA/o.jpg",
"is_closed": false,
"name": "North India Restaurant",
"review_count": 615,
"transactions": ["pickup", "restaurant_reservation"]
},
// ...
]
}
Here is my Business model :
class Business: Decodable, Identifiable, ObservableObject {
#Published var imageData: Data?
var id: String?
var alias: String?
var name: String?
var imageUrl: String?
var isClosed: Bool?
var url: String?
var reviewCount: Int?
var categories: [Category]?
var rating: Double?
var coordinates: Coordinate?
var transactions: [String]?
var price: String?
var location: Location?
var phone: String?
var displayPhone: String?
var distance: Double?
enum CodingKeys: String, CodingKey {
case imageUrl = "image_url"
case isClosed = "is_closed"
case reviewCount = "review_count"
case displayPhone = "display_phone"
case id
case alias
case name
case url
case categories
case rating
case coordinates
case transactions
case price
case location
case phone
case distance
}
func getImageData() {
// Check that image url isn't nil
guard imageUrl != nil else {
return
}
// Download the data for the image
if let url = URL(string: imageUrl!) {
// Get a session
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { (data, response, error) in
if error == nil {
DispatchQueue.main.async {
// Set the image data
self.imageData = data!
}
}
}
dataTask.resume()
}
}
static func getTestData() -> Business {
let b = Business()
return b
}
}
struct Category: Decodable {
var alias: String?
var title: String?
}
Here an example of my code :
struct BusinessDetail: View {
var business: Business
#State private var showDirections = false
var body: some View {
VStack (alignment: .leading) {
VStack (alignment:.leading, spacing:0) {
GeometryReader() { geometry in
// Business image
let uiImage = UIImage(data: business.imageData ?? Data())
Image(uiImage: uiImage ?? UIImage())
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
.ignoresSafeArea(.all, edges: .top)
// Open / closed indicator
ZStack (alignment: .leading) {
Rectangle()
.frame(height: 35)
.foregroundColor(business.isClosed! ? Color("icon-primary") : Color("background"))
Text(business.isClosed! ? "Closed" : "Open")
.foregroundColor(.white)
.font(.textHeader)
.padding(.leading)
}
}
Group {
HStack {
BusinessTitle(business: business)
.padding()
Spacer()
}
// Phone
HStack {
Text("Phone:")
.bold()
Text(business.displayPhone ?? "")
Spacer()
Link("Call", destination: URL(string: "tel:\(business.phone ?? "")")!)
}
.padding()
// Transactions
if business.transactions != nil {
ForEach(business.transactions!, id: \.self) { transaction in
Text(transaction)
.font(.bodyParagraph)
}
}

Related

Swift JSON objects decoding error. How to write it correctly?

I have a data decoding error. How to write it correctly?
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
My JSON object:
struct LeagueDocument: Codable {
var id: LeagueId
var name: LeagueName
var area: Area
var startDate: String
var endDate: String
init(id: LeagueId, name: LeagueName, area: Area, startDate: String, endDate: String) {
self.id = id
self.name = name
self.area = area
self.startDate = startDate
self.endDate = endDate
}
}
enum Area: String, Codable {
case POLAND, ENGLAND, FRANCE, SPAIN, GERMANY, ITALY, EUROPE, UEFA_CHAMPIONS_LEAGUE, EUROPE_LEAGUE
}
JSON which I got from server:
[
{
"id": "ba4ddfd6-cb91-4ea7-853d-79be89917445",
"name": "Liga 1 ",
"area": "POLAND",
"startDate": "2021-02-28",
"endDate": "2022-02-28"
},
{
"id": "4061b62b-dd57-4916-adf4-e0874ec767b1",
"name": "Liga 2",
"area": "POLAND",
"startDate": "2021-02-01",
"endDate": "2021-05-11"
}
]
My code:
func getAllActiveLeagues(completion: #escaping ([LeagueDocument]) -> Void) {
(...)
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { print(error!.localizedDescription); return }
if data != nil {
do {
let decodedData = try decoder.decode([LeagueDocument].self, from: data!)
completion(decodedData)
} catch {
print(error)
}
} else {
print("Data is nil")
}
}.resume()

Display items from nested array in SwiftUI

I'm trying to decode Yelp's JSON. Within the file there's a nested array of categories. Some businesses have 1, others may have 3.
Here's an example
{
"businesses": [
{
"id": "4jW-ZDeCPIl9aXvTWcATlA",
"alias": "the-delaney-hotel-orlando",
"name": "The Delaney Hotel",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/fikUF4yC5J63f3EOCZ8uOw/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/the-delaney-hotel-orlando?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 13,
"categories": [
{
"alias": "hotels",
"title": "Hotels"
},
{
"alias": "venues",
"title": "Venues & Event Spaces"
}
],
"rating": 5.0,
...etc
}
]
My data model is set up this way
struct BusinessesResponse: Codable {
enum CodingKeys: String, CodingKey {
case restaurants = "businesses"
}
let restaurants: [RestaurantResponse]
}
struct RestaurantResponse: Codable, Identifiable, Equatable {
let id: String
var name: String
var image_url: String
var is_closed: Bool
var review_count: Int
var rating: Double
var distance: Double
var price: String?
var display_phone: String?
var categories: [HotelCategory]
var coordinates: HotelCoordinates
var location: HotelLocation
var transactions: [String]
}
struct HotelCategory: Hashable, Codable {
var title: String
}
struct HotelCoordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
struct HotelLocation: Hashable, Codable {
var address1: String
var city: String
var state: String
var zip_code: String
}
I'm able to display everything other than the first pair in the categories array. My guess is that I would need to set a ForEach statement to display all available information from that array, but I'm not sure how to set that up correctly.
Here's what I have currently in abbreviated form
var category: [HotelCategory]
var body: some View {
HStack {
Text("\(category[0].title)")
}
}
Obviously that would only return the first child of that nested array. How would I dynamically account for that nested array having multiple children and then displaying them?
You can use ForEach this way:
ForEach(category, id: \.self) { category in
HStack {
Text("\(category.title)")
}
}
Note: if you're not sure that categories are unique, it might be better to conform HotelCategory to Identifiable and use id: \.id.
I'd also recommend following the convention and naming arrays with plural names, i.e.:
var categories: [HotelCategory] // instead of `category`

SwiftUI - Loop through and list items in nested array in Text element

I'm using Swift to call Yelp's API to return local business information. In the JSON response, there are multiple nested arrays, i.e. "Category" that describes what type of business it is. I'm able to return the first item in the array, but I'm lost on how to use ForEach to return each individual item in the array.
The response looks like this:
{
"businesses": [
"id": XP5BuE79...,
"categories" : [
{
"alias": "golfcourse",
"title": "Golf Course"
},
{
"alias": "clubhouse",
"title": "Clubhouse"
}
],
....
}
My data model
struct BusinessesResponse: Codable {
enum CodingKeys: String, CodingKey {
case restaurants = "businesses"
}
let restaurants: [RestaurantResponse]
}
struct RestaurantResponse: Codable, Identifiable {
let id: String
var name: String
var image_url: String
var is_closed: Bool
var review_count: Int
var rating: Double
var distance: Double
var price: String?
var display_phone: String
var categories: [RestaurantCategory]
}
Currently, I'm able to return the first item in the "categories" array by using
VStack {
ForEach(fetcher.businesses) { restaurant in
VStack (alignment: .leading) {
WebImage(url: URL(string: restaurant.image_url))
.resizable()
.indicator(.activity) // Activity Indicator
.transition(.fade(duration: 0.5)) // Fade Transition with duration
.scaledToFill()
.frame(width: 400, height: 200, alignment: .center)
.clipped()
Text(restaurant.name)
.fontWeight(.bold)
HStack(spacing: 5.0) {
Text(restaurant.price ?? "No Pricing Info")
.font(.caption)
Text("·")
//Category displaying only first in nested array
Text(restaurant.categories[0].title)
.font(.caption)
Text("·")
Image(systemName: "star.fill")
.font(.caption)
Text("\(restaurant.rating, specifier: "%.1f")")
.font(.caption)
Text("(\(restaurant.review_count))")
.font(.caption)
Spacer()
}
}
}.padding(.bottom)
.padding(.horizontal, 20)
}

How to add arrays of objects when deserializing with SwiftyJSON

I am working on deserializing JSON with SwiftyJSON and Swift 5.1.
I have a set of data like below
"name": "American Standard",
"number": 1,
"subcategories": [
{
"name": "American Light Lager",
"letter": "A",
"guidelines": {
"overallImpression": "Some String",
"aroma": "Some String",
"appearance": "Some String",
"flavor": "Some String",
"mouthfeel": "Some String",
"comments": "Some String",
"history": "Some String",
"ingredients": "Some String",
"comparison": "Some String",
"vitalStatistics": {
"og": "1.028 - 1.040",
"fg": "0.998 - 1.008",
"abv": "2.8 - 4.2%",
"ibu": "8 - 12",
"srm": "2 - 3"
}
},
"commercialExamples": [
{
"name": "name1"
},
{
"name": "name2"
},
{
"name": "name3"
},
],
"tags": [
{
"tag": "tag1"
},
{
"tag": "tag2"
},
]
},
I am using struct to hold all the data, shown below.
struct Beers
{
var name: String
var number: Int
var subcategories: Subcategory
}
struct Subcategory
{
var name: String
var letter: String
var guidelines: Guidelines
var commercialExamples: [CommercialExample]
var tags: [Tag]
}
struct Guidelines
{
var overallImpression: String
var aroma: String
var appearance: String
var flavor: String
var mouthfeel: String
var comments: String
var history: String
var ingredients: String
var comparison: String
var vitalStatistics: VitalStatistics
}
struct VitalStatistics
{
var og: String
var fg: String
var abv: String
var ibu: String
var srm: String
}
struct CommercialExample : Hashable
{
var name: String
func hash(into hasher: inout Hasher)
{
hasher.combine(name)
}
}
struct Tag : Hashable
{
var tag: String
func hash(into hasher: inout Hasher)
{
hasher.combine(tag)
}
}
And for my deserialization code, I have this.
for (index, dict) in jsonObject
{
let thisBeer = Beers(name: dict["name"].stringValue, number: dict["number"].intValue, subcategories: Subcategory(name: dict["name"].stringValue, letter: dict["letter"].stringValue, guidelines: Guidelines(overallImpression: dict["overallImpression"].stringValue, aroma: dict["aroma"].stringValue, appearance: dict["appearance"].stringValue, flavor: dict["flavor"].stringValue, mouthfeel: dict["mouthfeel"].stringValue, comments: dict["comments"].stringValue, history: dict["history"].stringValue, ingredients: dict["ingredients"].stringValue, comparison: dict["comparison"].stringValue, vitalStatistics: VitalStatistics(og: dict["og"].stringValue, fg: dict["fg"].stringValue, abv: dict["abv"].stringValue, ibu: dict["ibu"].stringValue, srm: dict["srm"].stringValue)), commercialExamples: CommercialExample(name: dict["name"].stringValue), tags: Tag(tags: dict["tags"].stringValue)))
beers.append(thisBeer)
}
This is where I am stuck. I am much more familiar with C# and .net. I just don't know how to go about looping through the commercialExamples and tags and creating the objects from the data and populating the arrays with them. What is the proper way of doing this with Swift and SwiftyJSON?
You must first use JSONSerializable protocols to decode your JSON data into the object. For example in your case:
struct Beers: JSONSerializable {
var name: String?
var number: Int?
var subcategories: Subcategory?
enum CodingKeys: String, CodingKey {
case name
case number
case subcategories
}
}
extension Beers: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try values.decodeIfPresent(String?.self, forKey: .name) {
if let value = value {
name = value
}
}
if let value = try values.decodeIfPresent(Int?.self, forKey: .number) {
if let value = value {
number = value
}
}
if let value = try values.decodeIfPresent(Subcategory?.self, forKey: .subcategories) {
if let value = value {
subcategories = value
}
}
}
}
Then you can make an object from JSON data like this:
do {
let model = try JSONDecoder().decode(Beers.self, from: jsonData)
} catch let error {
print(error)
}

Swift 5 - Display nested JSON with Arrays of Objects

I'm trying to figure out the correct Syntax (or where I am going wrong?) for displaying returned multi nested json that has arrays containing further objects.
I can manage to get the nested objects to work but I get an error when trying to get the objects inside the array.
Here is my Struct:
import Foundation
struct spaceXLaunch: Codable {
var flight_number: Int
var mission_name: String?
var launch_date_local: String?
var rocket: rocketResponse
var launch_site: launchSiteResponse
var links: linksResponse
var launch_success: Bool?
var details: String?
}
extension spaceXLaunch: Identifiable {
var id: Int { return flight_number }
}
struct rocketResponse: Codable {
var rocket_name: String
var rocket_id: String
var first_stage: firstStageResponse
var second_stage: secondStageResponse
}
struct firstStageResponse: Codable {
var cores: [coresResponse]
}
struct coresResponse: Codable {
var landing_vehicle: String?
var land_success: Bool?
var reused: Bool?
var landing_type: String?
}
struct secondStageResponse: Codable {
var payloads: [payloadsResponse]
}
struct payloadsResponse: Codable {
var payload_id: String?
var payload_type: String?
var payload_mass_kg: Double?
}
struct launchSiteResponse: Codable {
var site_name: String
}
struct linksResponse: Codable {
var missionPatch: String?
var missionPatchSml: String?
private enum CodingKeys: String, CodingKey {
case missionPatch = "mission_patch"
case missionPatchSml = "mission_patch_small"
}
}
Here is the SwiftUI code Im using to render the view
import SwiftUI
struct listView: View {
#ObservedObject var fetcher = LaunchDataFetcher()
var body: some View {
List(fetcher.launches) { spaceXLaunch in
VStack (alignment: .leading) {
Text(spaceXLaunch.mission_name ?? "No Data Found")
Spacer()
Text(spaceXLaunch.details ?? "No Data Found")
.font(.system(size: 11))
Spacer()
Text(spaceXLaunch.launch_date_local ?? "No Data Found")
Spacer()
Text(spaceXLaunch.rocket.rocket_name)
Spacer()
///This line below comes back with an error of "Cannot subscript a value of incorrect or ambiguous type"
Text(spaceXLaunch.rocket.first_stage.cores[0].landing_type)
Spacer()
}
}
}
}
struct listView_Previews: PreviewProvider {
static var previews: some View {
listView()
.environment(\.colorScheme, .dark)
}
}
Here is a sample of the Json data returned for the api
{
"flight_number": 24,
"mission_name": "CRS-7",
"mission_id": [
"EE86F74"
],
"launch_year": "2015",
"launch_date_unix": 1435501260,
"launch_date_utc": "2015-06-28T14:21:00.000Z",
"launch_date_local": "2015-06-28T10:21:00-04:00",
"is_tentative": false,
"tentative_max_precision": "hour",
"tbd": false,
"launch_window": 0,
"rocket": {
"rocket_id": "falcon9",
"rocket_name": "Falcon 9",
"rocket_type": "v1.1",
"first_stage": {
"cores": [
{
"core_serial": "B1018",
"flight": 1,
"block": 1,
"gridfins": true,
"legs": true,
"reused": false,
"land_success": null,
"landing_intent": true,
"landing_type": "ASDS",
"landing_vehicle": "OCISLY"
}
]
},
"second_stage": {
"block": 1,
"payloads": [
{
"payload_id": "CRS-7",
"norad_id": [],
"reused": false,
"cap_serial": "C109",
"customers": [
"NASA (CRS)"
],
"nationality": "United States",
"manufacturer": "SpaceX",
"payload_type": "Dragon 1.1",
"payload_mass_kg": 2477,
"payload_mass_lbs": 5460.9,
"orbit": "ISS",
"orbit_params": {
"reference_system": "geocentric",
"regime": "low-earth",
"longitude": null,
"semi_major_axis_km": null,
"eccentricity": null,
"periapsis_km": null,
"apoapsis_km": null,
"inclination_deg": 51.6,
"period_min": null,
"lifespan_years": null,
"epoch": null,
"mean_motion": null,
"raan": null,
"arg_of_pericenter": null,
"mean_anomaly": null
},
"mass_returned_kg": null,
"mass_returned_lbs": null,
"flight_time_sec": 139,
"cargo_manifest": "https://www.nasa.gov/sites/default/files/atoms/files/spacex_crs-7_mission_overview.pdf",
"uid": "UerI6qmZTU2Fx2efDFm3QQ=="
}
]
},
"fairings": null
},
"ships": [
"ELSBETH3",
"GOQUEST",
"GOSEARCHER"
],
"telemetry": {
"flight_club": null
},
"launch_site": {
"site_id": "ccafs_slc_40",
"site_name": "CCAFS SLC 40",
"site_name_long": "Cape Canaveral Air Force Station Space Launch Complex 40"
},
"launch_success": false,
"launch_failure_details": {
"time": 139,
"altitude": 40,
"reason": "helium tank overpressure lead to the second stage LOX tank explosion"
},
"links": {
"mission_patch": "https://images2.imgbox.com/47/39/stH98Qy1_o.png",
"mission_patch_small": "https://images2.imgbox.com/d0/22/gyTVYo21_o.png",
"reddit_campaign": null,
"reddit_launch": "https://www.reddit.com/r/spacex/comments/3b27hk",
"reddit_recovery": null,
"reddit_media": "https://www.reddit.com/r/spacex/comments/3berj3",
"presskit": "https://www.nasa.gov/sites/default/files/atoms/files/spacex_nasa_crs-7_presskit.pdf",
"article_link": "https://spaceflightnow.com/2015/06/28/falcon-9-rocket-destroyed-in-launch-mishap/",
"wikipedia": "https://en.wikipedia.org/wiki/SpaceX_CRS-7",
"video_link": "https://www.youtube.com/watch?v=PuNymhcTtSQ",
"youtube_id": "PuNymhcTtSQ",
"flickr_images": [
"https://farm1.staticflickr.com/344/19045370790_f20f29cd8d_o.jpg",
"https://farm1.staticflickr.com/287/18999110808_6e153fed64_o.jpg"
]
},
"details": "Launch performance was nominal until an overpressure incident in the second-stage LOX tank, leading to vehicle breakup at T+150 seconds. The Dragon capsule survived the explosion but was lost upon splashdown because its software did not contain provisions for parachute deployment on launch vehicle failure.",
"upcoming": false,
"static_fire_date_utc": "2015-06-26T05:00:00.000Z",
"static_fire_date_unix": 1435294800,
"timeline": {
"webcast_liftoff": 61,
"go_for_prop_loading": -2280,
"rp1_loading": -2100,
"stage1_lox_loading": -2100,
"stage2_lox_loading": -960,
"engine_chill": -420,
"prelaunch_checks": -60,
"propellant_pressurization": -60,
"go_for_launch": -45,
"ignition": -3,
"liftoff": 0,
"maxq": 60,
"meco": 180,
"stage_sep": 180,
"second_stage_ignition": 180,
"seco-1": 540,
"dragon_separation": 600,
"dragon_solar_deploy": 720,
"dragon_bay_door_deploy": 8400
},
"crew": null
},
Thanks to Chris for pointing out that adding an optional (??) at the end of this line
Text(spaceXLaunch.rocket.first_stage.cores[0].landing_type)
so it looks like this:
Text(spaceXLaunch.rocket.first_stage.cores[0].landing_type ?? "No Data Found")
and it all works.
Generate your JSONModel structs with following tool https://app.quicktype.io . Then validated your logic.

Resources