yasin yasin - 2 months ago 103
Swift Question

Error: 'Cannot call value of non-function type' on Swift 3

The second and third lines of the following code were working on swift 2.3 and since I updated to swift 3, I've been getting the error Cannot call value of non-function type 'Any?!' for both of them:

let dic = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableLeaves) as! NSDictionary

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
let lon = ((((dic["results"] as AnyObject).value(forKey: "geometry") as AnyObject).value(forKey: "location") as AnyObject).value(forKey: "lng") as AnyObject).object(0) as! Double

self.delegate.locateWithLongitude(lon, andLatitude: lat, andTitle: self.searchResults[(indexPath as NSIndexPath).row])


This is the callback from a google maps search.

The JSON that the method is reading is this:

{
"results" : [
{
"address_components" : [
{
"long_name" : "São Paulo",
"short_name" : "São Paulo",
"types" : [ "locality", "political" ]
},
{
"long_name" : "São Paulo",
"short_name" : "São Paulo",
"types" : [ "administrative_area_level_2", "political" ]
},
{
"long_name" : "State of São Paulo",
"short_name" : "SP",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "Brazil",
"short_name" : "BR",
"types" : [ "country", "political" ]
}
],
"formatted_address" : "São Paulo, State of São Paulo, Brazil",
"geometry" : {
"bounds" : {
"northeast" : {
"lat" : -23.3566039,
"lng" : -46.36508449999999
},
"southwest" : {
"lat" : -24.0082209,
"lng" : -46.825514
}
},
"location" : {
"lat" : -23.5505199,
"lng" : -46.63330939999999
},
"location_type" : "APPROXIMATE",
"viewport" : {
"northeast" : {
"lat" : -23.3566039,
"lng" : -46.36508449999999
},
"southwest" : {
"lat" : -24.0082209,
"lng" : -46.825514
}
}
},
"partial_match" : true,
"place_id" : "ChIJ0WGkg4FEzpQRrlsz_whLqZs",
"types" : [ "locality", "political" ]
}
],
"status" : "OK"
}


Does anyone knows what I have do change to make it work?

Answer

There are a couple of things which are pretty bad in Swift when parsing JSON:

  • casting to NSDictionary or NSArray, which have no type information.
  • casting to AnyObject which is very unspecified.
  • valueForKey which is a key-value coding method and not necessary in this case.
  • The option mutableleaves which is meaningless when using Swift types and never needed when the objects are only read.

Sue all tutorials which suggest that poorly programming habit. ;-)

Consider that AnyObject has been replaced with Any in Swift 3.

JSON supports only two collection types, array – represented by [] – and dictionary – represented by {}.

To parse JSON reliably you have to read the JSON carefully to identify the structure and tell the compiler the types of the nodes.

Your main mistake is the wrong type of the value for key results which is an array.

This code checks for the existence of all keys with optional bindings and also if the array is not empty and the dictionary for location contains 2 items:

if let jsonObject = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:Any],
  let results = jsonObject["results"] as? [[String:Any]], !results.isEmpty,
  let geometry = results[0]["geometry"] as? [String:Any],
  let location = geometry["location"] as? [String:Double],
  let lat = location["lat"], let lng = location["lng"] {

  print("lat: \(lat) - lng: \(lng)")
} 

Apple has published a comprehensive article Working with JSON in Swift

Comments