Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse dictionary in swift 3.0

I am using google places api to get latitude and longitude , but not able to parse dictionary. I am using following code

 let lat = ((((dic["results"] as! AnyObject).value(forKey: "geometry") as AnyObject).value(forKey: "location") as AnyObject).value(forKey: "lat") as AnyObject).object(0) as! Double

Error: Cannot call value of non-functioning type 'Any?!'

I have also checked with nsdictionary instead of Anyobject but didn't worked . Can any one know how to properly get lat from above code.

Responce :

{
results =     (
            {
        "address_components" =             (
                            {
                "long_name" = Palermo;
                "short_name" = Palermo;
                types =                     (
                    locality,
                    political
                );
            },
                            {
                "long_name" = "Province of Palermo";
                "short_name" = PA;
                types =                     (
                    "administrative_area_level_2",
                    political
                );
            },
                            {
                "long_name" = Sicily;
                "short_name" = Sicily;
                types =                     (
                    "administrative_area_level_1",
                    political
                );
            },
                            {
                "long_name" = Italy;
                "short_name" = IT;
                types =                     (
                    country,
                    political
                );
            }
        );
        "formatted_address" = "Palermo, Italy";
        geometry =             {
            bounds =                 {
                northeast =                     {
                    lat = "38.219548";
                    lng = "13.4471566";
                };
                southwest =                     {
                    lat = "38.0615392";
                    lng = "13.2674205";
                };
            };
            location =                 {
                lat = "38.1156879";
                lng = "13.3612671";
            };
            "location_type" = APPROXIMATE;
            viewport =                 {
                northeast =                     {
                    lat = "38.2194316";
                    lng = "13.4471566";
                };
                southwest =                     {
                    lat = "38.0615392";
                    lng = "13.2674205";
                };
            };
        };
        "place_id" = ChIJmdBOgcnoGRMRgNg7IywECwo;
        types =             (
            locality,
            political
        );
    }
);
status = OK;

}

like image 710
user2702179 Avatar asked Jan 18 '17 06:01

user2702179


2 Answers

You have to parse the JSON in multiple steps, for clarity I'd recommend to use a type alias:

typealias JSONDictionary = [String:Any]

    let results = dic["results"] as? [JSONDictionary] {
      for result in results {
        if let geometry = result["geometry"] as? JSONDictionary,
           let location = geometry["location"] as? JSONDictionary {
              if let lat = location["lat"] as? Double,
                 let lng = location["lng"] as? Double {
                   print(lat, lng)
              }
        }
     }
   }

A few rules in Swift :

  • Do not use NSDictionary / NSArray at all unless you have absolutely no choice.
  • A JSON dictionary in Swift 3 is [String:Any], a JSON array [[String:Any]].
  • Cast Any types down as specific as possible.
  • When parsing JSON from a remote server use always optional bindings to avoid runtime errors.
like image 76
vadian Avatar answered Sep 30 '22 17:09

vadian


Kristijan's answer is somewhat correct, though it ignores a lot of important syntax and optional type casting. Assuming somewhere in your code you are making the actual API call:

class SomeClass {
    func getLocationData() {
        //do your api call stuff here and get "result"
        let location: Location? = Location(json: result as? [String:Any])
    }
}

You can handle the response in some way, perhaps with a struct:

struct Location {
    var lat: Double
    var long: Double

    init(lat: Double, long: Double) {
        self.lat = lat
        self.long = long
    }

    init?(json: [String:Any]?) {
        guard let results = json?["results"] as? [Any],
            let first = results[0] as? [String:Any],
            let geometry = first["geometry"] as? [String:Any],
            let location = geometry["location"] as? [String:Any],
            let latitude = location["lat"] as? Double,
            let longitude = location["lng"] as? Double else {
            return nil
        }
        self.lat = latitude
        self.long = longitude
    }
}

The point in the struct initializer is using conditional unwrapping to type cast your JSON response object's respective members to their appropriate types.

Note that this can vary significantly based on the actual JSON object you are receiving from the API. It would be helpful if you could post the actual JSON response in raw text in addition to the Swift code you've already attempted to write to parse it. My response does the best it can based on the information you provided. I could probably write something with a higher degree of confidence were you to supply the raw JSON object from your API response.

Edit: Following is a Playground snippet which retrieves and parses the result from the Google Places API

import PlaygroundSupport
import Foundation

PlaygroundPage.current.needsIndefiniteExecution = true

struct Location {
    var lat: Double
    var long: Double

    init(lat: Double, long: Double) {
        self.lat = lat
        self.long = long
    }

    init?(json: [String:Any]?) {
        guard let results = json?["results"] as? [Any],
            let first = results[0] as? [String:Any],
            let geometry = first["geometry"] as? [String:Any],
            let location = geometry["location"] as? [String:Any],
            let latitude = location["lat"] as? Double,
            let longitude = location["lng"] as? Double else {
            return nil
        }
        self.lat = latitude
        self.long = longitude
    }
}

func getLocationData() {
    let base = "https://maps.googleapis.com/maps/api/geocode/json"
    let args = [
        "address=%50%61%6C%65%72%6D%6F%2C%20%50%72%6F%76%69%6E%63%65%20%6F%66%20%50%61%6C%65%72%6D%6F%2C%20%49%74%61%6C%79",
        "sensor=false",
    ]

    let urlStr = "\(base)?\(args.joined(separator: "&"))"
    guard let url = URL(string: urlStr) else { return }
    var request = URLRequest(url: url)
    request.httpMethod = "GET"

    URLSession.shared.dataTask(with: request,
        completionHandler: { (data, response, error) -> Void in
            guard let jsonData = data else { return }
            if let jsonResult = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as? [String:Any] {
                let location: Location? = Location(json: jsonResult)
                print("Location: \(location?.lat), \(location?.long)")
            }
        }
    ).resume()
}

getLocationData()
like image 30
Michael Fourre Avatar answered Sep 30 '22 19:09

Michael Fourre