Coding_Noob
Coding_Noob

Reputation: 91

Why is my decoder not able to decode my api call with Alamofire in swift

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

Answers (1)

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

Related Questions