Reputation: 91
So I'm trying to to process my API call data into an object I'm calling PlaceModel.
The API returns a lot of data; but I only need name, and coordinate data for my purposes at the moment.
example of API data
--- redacted for brevity ---
{
"html_attributions" : [],
"next_page_token" : "AeJbb3dLZZ4oYIangr8wO9j6SE4_5L1NcYQC-qCKt1Y0saPFOzNc90E7bG8IDtj8DNlEeM6nWlClZgFd-YMtB4kGQYBLexZgjlX-nH9rN4UQHk9hk-Ha9zLpjY0GYiUsayMZKW5x7oAkQOXlmbARFxu2tR-dW5VB_dd0VkJkf7_6zeGeeje0UjQeqdk1Czr-gBgNxzmMj0adrj2_G0CxeJxk5eN645sdbZ8QJ-BChqkUegJ8l9V7dO4nRCfPz5u_paqoCEcVOJqYMyiZsc3ibBxNowY-uH2PCbpyXcM186CenqBIG088TiyulBFL4Dq1zDxXbZ5asSH4r-u8UtWpdpEcpzAJMjIT4W64vvXYZUT3_918VgENIyCEkPQVebjZUF0X2USykC-ZdlsB2BMvxjKlwx5Q_7_yKYy3Gnxq6T6jTgGisAvNXQ",
"results" : [
{
"business_status" : "OPERATIONAL", // FIRST BUSINESS
"geometry" : {
"location" : {
"lat" : 49.24665,
"lng" : -122.7504486 // HERE IS SOME LOCATION INFO
},
"viewport" : {
"northeast" : {
"lat" : 49.24808953029149,
"lng" : -122.7490256697085
},
"southwest" : {
"lat" : 49.2453915697085,
"lng" : -122.7517236302915
}
}
},
"icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/school-71.png",
"icon_background_color" : "#7B9EB0",
"icon_mask_base_uri" : "https://maps.gstatic.com/mapfiles/place_api/icons/v2/school_pinlet",
"name" : "Ascension Martial Arts", // HERE IS A NAME
"photos" : [
{
"height" : 1668,
"html_attributions" : [
"\u003ca href=\"https://maps.google.com/maps/contrib/115539551484221381565\"\u003eAscension Martial Arts\u003c/a\u003e"
],
"photo_reference" : "AeJbb3dunz49PLKAwwhcbLid5d2yWHftgLOY5sdMPLXhCKohiOcfvinKkmFxIht8ZBSNgxTFVJCCbnHxOG7ROlt093iHntMlX4O80ihQ0MI80u7u_TvvKMcwu3os0tgfr84KaNVP1KzNrzTiS1GUF0S82OM43NEuWoMjBW5o2PmhH-Ke-tj4",
"width" : 2500
}
],
"place_id" : "ChIJST1805B2hlQRASo6CA1ejKw",
"plus_code" : {
"compound_code" : "66WX+MR Port Coquitlam, BC, Canada",
"global_code" : "84XV66WX+MR"
},
"rating" : 4.9,
"reference" : "ChIJST1805B2hlQRASo6CA1ejKw",
"scope" : "GOOGLE",
"types" : [ "gym", "health", "point_of_interest", "establishment" ],
"user_ratings_total" : 107,
"vicinity" : "109-1320 Kingsway Avenue, Port Coquitlam"
},
{
"business_status" : "OPERATIONAL", // NEXT BUSINESS
--- redacted for brevity ---
So I really only need those 2 pieces of data for marking spots on my map.
Now I'm just trying to debug it and get it to work making Struct objects but I'm stuck.
networkingLayerAF.swift
import Foundation
import Alamofire
class NetworkingLayerAF{
func getPlaces() {
let longitudeX = "-122.735960"
let latitudeY = "49.252400"
let url = URL(string:"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(latitudeY),\(longitudeX)&radius=150000&types=gym&key=NFS")!
// AF.request(url).response{ response in
// debugPrint(response)
// }
print("LAUNCH ALAMO")
AF.request(url).responseDecodable(of: [PlaceModel].self){ response in
switch response.result {
case .success(let items):
print(items)
case .failure(let error):
print(error.localizedDescription)
}
}
}//
}// end class
Error I'm getting when it runs in console.
LAUNCH ALAMO
2022-07-31 17:06:24.217509-0700 MyAPP[14661:1355709] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
Response could not be decoded because of error:
The data couldn’t be read because it isn’t in the correct format.
PlaceModel
import Foundation
struct PlaceModel: Decodable {
var name: String
var coordinate: Coordinate
enum CodingKeys: String, CodingKey {
case name = "name"
case coordinate
}
}
struct Coordinate {
var latitude: Double
var longitude: Double
enum CodingKeys: String, CodingKey {
case latitude
case longitude
}
}
extension Coordinate: Decodable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: Coordinate.CodingKeys.self)
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
}
}
Earlier I was trying to use this websites json to swift converter which gave me a different looking struct class
placeModel - I added quite a few decodable tags onto the converter's code.
import Foundation
import CoreLocation
struct Welcome1: Decodable{
let nextPageToken: String
let results: [Result]
let status: String
}
// MARK: - Result
struct Result : Decodable{
let businessStatus: BusinessStatus
let geometry: Geometry
let icon: String
let iconBackgroundColor: IconBackgroundColor
let iconMaskBaseURI: String
let name: String
let photos: [Photo]?
let placeID: String
let plusCode: PlusCode
let rating: Double?
let reference: String
let scope: Scope
let types: [TypeElement]
let userRatingsTotal: Int?
let vicinity: String
let openingHours: OpeningHours?
}
enum BusinessStatus: CodingKey , Decodable{
case operational
}
// MARK: - Geometry
struct Geometry : Decodable {
let location: Location
let viewport: Viewport
}
// MARK: - Location
struct Location : Decodable{
let lat, lng: Double
}
// MARK: - Viewport
struct Viewport : Decodable{
let northeast, southwest: Location
}
enum IconBackgroundColor: CodingKey, Decodable {
case the7B9Eb0
}
// MARK: - OpeningHours
struct OpeningHours : Decodable{
let openNow: Bool
}
// MARK: - Photo
struct Photo : Decodable{
let height: Int
let htmlAttributions: [String]
let photoReference: String
let width: Int
}
// MARK: - PlusCode
struct PlusCode : Decodable{
let compoundCode, globalCode: String
}
enum Scope: CodingKey , Decodable{
case google
}
enum TypeElement: CodingKey , Decodable {
case establishment
case gym
case health
case pointOfInterest
case school
}
But I kept getting pretty much the same error
LAUNCH ALAMO
Response could not be decoded because of error:
The data couldn’t be read because it isn’t in the correct format.
One thing I will note; when I use print(error) instead of LocalizedDescription I get this error:
LAUNCH ALAMO
2022-07-31 17:33:09.489244-0700 Body Fat Calculator[15033:1371997] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))))
So I edited the code a little to: AF.request(url).responseDecodable(of: Welcome1.self) // Not [Welcome1] now.
and I'm getting some new errors
Welcome1
LAUNCH ALAMO
responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "nextPageToken", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"nextPageToken\", intValue: nil) (\"nextPageToken\").", underlyingError: nil))))
PlaceModel
LAUNCH ALAMO
2022-07-31 17:43:32.518882-0700 Body Fat Calculator[15209:1379339] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))))
Ok this getting a bit long, I'm going to keep trying stuff any insights appreciated happy to provide more info if needed :)
Thank you :D
Upvotes: 0
Views: 669
Reputation: 36304
this example code does not use Alamofire, but should give you enough to get your data decoded and extract the name, and coordinate as per your question.
Since I do not have your key
, I included the json you show as a simulated response.
The crux here is to match your struct models to the JSON data, otherwise you cannot decode it without errors. Works well for me.
You will need to find from the server docs, which fields are optional and adjust the various struct models accordingly.
import Foundation
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = BusinessService()
var body: some View {
List (viewModel.businessList) { bisnes in
VStack {
Text(bisnes.name)
Text("lat: \(bisnes.geometry.location.lat)")
Text("lon: \(bisnes.geometry.location.lng)")
}
}
.task {
do {
try await viewModel.getData(lat: -122.735960, lon: 49.252400)
} catch{
print("---> ContentView error: \(error)")
}
}
}
}
class BusinessService: ObservableObject{
@Published var businessList: [Business] = []
let nfs = "xxxxxxx" // <--- here your key
func getData2(lat: Double, lon: Double) async throws {
if let url = URL(string: "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(lat),\(lon)&radius=150000&types=gym&key=\(nfs)") {
let (data, _) = try await URLSession.shared.data(from: url)
Task{@MainActor in
let responseData = try JSONDecoder().decode(ServerResponse.self, from: data)
self.businessList = responseData.results
// print(businessList) // <-- for testing
}
}
}
// simulated
func getData(lat: Double, lon: Double) async throws {
let json = """
{
"html_attributions" : [],
"next_page_token" : "AeJbb3dLZZ4oYIangr8wO9j6SE4_5L1NcYQC-qCKt1Y0saPFOzNc90E7bG8IDtj8DNlEeM6nWlClZgFd-YMtB4kGQYBLexZgjlX-nH9rN4UQHk9hk-Ha9zLpjY0GYiUsayMZKW5x7oAkQOXlmbARFxu2tR-dW5VB_dd0VkJkf7_6zeGeeje0UjQeqdk1Czr-gBgNxzmMj0adrj2_G0CxeJxk5eN645sdbZ8QJ-BChqkUegJ8l9V7dO4nRCfPz5u_paqoCEcVOJqYMyiZsc3ibBxNowY-uH2PCbpyXcM186CenqBIG088TiyulBFL4Dq1zDxXbZ5asSH4r-u8UtWpdpEcpzAJMjIT4W64vvXYZUT3_918VgENIyCEkPQVebjZUF0X2USykC-ZdlsB2BMvxjKlwx5Q_7_yKYy3Gnxq6T6jTgGisAvNXQ",
"results" : [
{
"business_status" : "OPERATIONAL",
"geometry" : {
"location" : {
"lat" : 49.24665,
"lng" : -122.7504486
},
"viewport" : {
"northeast" : {
"lat" : 49.24808953029149,
"lng" : -122.7490256697085
},
"southwest" : {
"lat" : 49.2453915697085,
"lng" : -122.7517236302915
}
}
},
"icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/school-71.png",
"icon_background_color" : "#7B9EB0",
"icon_mask_base_uri" : "https://maps.gstatic.com/mapfiles/place_api/icons/v2/school_pinlet",
"name" : "Ascension Martial Arts",
"photos" : [
{
"height" : 1668,
"html_attributions" : ["Arts"],
"photo_reference" : "AeJbb3dunz49PLKAwwhcbLid5d2yWHftgLOY5sdMPLXhCKohiOcfvinKkmFxIht8ZBSNgxTFVJCCbnHxOG7ROlt093iHntMlX4O80ihQ0MI80u7u_TvvKMcwu3os0tgfr84KaNVP1KzNrzTiS1GUF0S82OM43NEuWoMjBW5o2PmhH-Ke-tj4",
"width" : 2500
}
],
"place_id" : "ChIJST1805B2hlQRASo6CA1ejKw",
"plus_code" : {
"compound_code" : "66WX+MR Port Coquitlam, BC, Canada",
"global_code" : "84XV66WX+MR"
},
"rating" : 4.9,
"reference" : "ChIJST1805B2hlQRASo6CA1ejKw",
"scope" : "GOOGLE",
"types" : [ "gym", "health", "point_of_interest", "establishment" ],
"user_ratings_total" : 107,
"vicinity" : "109-1320 Kingsway Avenue, Port Coquitlam"
}
]
}
"""
let data = json.data(using: .utf8)!
Task{@MainActor in
let responseData = try JSONDecoder().decode(ServerResponse.self, from: data)
self.businessList = responseData.results
print(businessList)
}
}
}
struct ServerResponse: Codable {
let htmlAttributions: [String]
let nextPageToken: String
let results: [Business]
enum CodingKeys: String, CodingKey {
case htmlAttributions = "html_attributions"
case nextPageToken = "next_page_token"
case results
}
}
// MARK: - Business
struct Business: Codable, Identifiable {
let id = UUID()
let businessStatus: String
let geometry: Geometry
let icon: String
let iconBackgroundColor: String
let iconMaskBaseURI: String
let name: String
let photos: [Photo]?
let placeID: String
let plusCode: PlusCode
let rating: Double?
let reference, scope: String
let types: [String]
let userRatingsTotal: Int?
let vicinity: String
enum CodingKeys: String, CodingKey {
case businessStatus = "business_status"
case geometry, icon
case iconBackgroundColor = "icon_background_color"
case iconMaskBaseURI = "icon_mask_base_uri"
case name, photos
case placeID = "place_id"
case plusCode = "plus_code"
case rating, reference, scope, types
case userRatingsTotal = "user_ratings_total"
case vicinity
}
}
// MARK: - Geometry
struct Geometry: Codable {
let location: Location
let viewport: Viewport
}
// MARK: - Location
struct Location: Codable {
let lat, lng: Double
}
// MARK: - Viewport
struct Viewport: Codable {
let northeast, southwest: Location
}
// MARK: - Photo
struct Photo: Codable {
let height: Int
let htmlAttributions: [String]
let photoReference: String
let width: Int
enum CodingKeys: String, CodingKey {
case height
case htmlAttributions = "html_attributions"
case photoReference = "photo_reference"
case width
}
}
// MARK: - PlusCode
struct PlusCode: Codable {
let compoundCode, globalCode: String
enum CodingKeys: String, CodingKey {
case compoundCode = "compound_code"
case globalCode = "global_code"
}
}
Using https://app.quicktype.io/
as you probably did, is a good idea.
However, you need to adjust what it gives you. For example, best
not to use the word Result
(because Swift already has this struct declared), I used Business
.
Upvotes: 1