JSON Parsing & Optionals in Swift 3 - ios

So i recently updated to Swift 3/XCode 8 and some of my code went hay-wire. I've read that some syntax changes have been made but I can't seem get this one right.
I make a request to Twitter and get JSON back:
func forLoadStats(completion: (AnyObject?, NSError?) -> Void)
{
var clientError: NSError?
let idString = api.getUserID()
let client = TWTRAPIClient()
let request = client.urlRequest(withMethod: "GET", url: "https://api.twitter.com/1.1/users/show.json", parameters: ["user_id" : 27446437], error: &clientError)
client.sendTwitterRequest(request)
{ (response, data, connectionError) in
if (connectionError == nil)
{
do {
if let json: Any = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [AnyObject]
{
if let json = json, let immage = json?["profile_image_url_https"] as? String
{
//Make ProfilePic edges round
self.profPic.layer.cornerRadius = 42
self.profPic.clipsToBounds = true
//let immage = image["profile_image_url_https"] as String
let _vImageUrl = immage.replacingOccurrences(of: "_normal", with: "")
let urlProfilePic = NSURL(string: _vImageUrl)
let urlPP = NSData(contentsOf: urlProfilePic! as URL)
self.profPic.image = UIImage(data: urlPP! as Data)
let ScrName = json["screen_name"] as! String
self.scrNameLabel.text = "#\(ScrName)"
//Populate Followers Label.text
let flwrVar = json["followers_count"] as! Int
self.followerLbl.text = "\(flwrVar)"
//Populate Following Label.text
let flwngVar = json["friends_count"] as! Int
self.followingLbl.text = "\(flwngVar)"
//Populate Bio
let bio = json["description"] as! String
self.bioLabel.text = "\(bio)"
//created at date
let accountAge = json["created_at"] as! String
self.createdLbl.text = "\(accountAge)"
let tweetCount = json["statuses_count"] as! Int
self.tweetCount.text = "\(tweetCount)"
let likes = json["favourites_count"] as! Int
self.likesCount.text = "\(likes)"
let lists = json["listed_count"] as! Int
self.listedCount.text = "\(lists)"
}
}
}
catch let error
{
print(error)
}
}
}
}
I get an error on the second "If let" statement that says: "initializer for conditional binding must have optional type not 'Any.
Can someone explain why this is?

Your JSON is obviously a dictionary, a JSON dictionary in Swift 3 is [String:Any]
You caused the error by the silly Any annotation (which is supposed to be Any? but is practically nonsensical) because it confuses the compiler.
If you use a do block, try without question mark but use optional binding:
...
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any] {
if let immage = json["profile_image_url_https"] as? String { ...

There are a couple of problems with the syntax. [AnyObject] will not work to use reference items such as json["profile_image_url_https"]. also, you are redeclaring json through let json = json, which makes it a non option in your let immage = json?["profile_image_url_https"] call, so that becomes a problem. This structure does not have any compiler errors
if let json = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
{
if let json = json, let immage = json["profile_image_url_https"] as? String
{
}
}
This does not have any compiler errors.

Related

JSON Parsing---> Could not cast value of type '__NSCFString' (0x1e5c38f90) to 'NSDictionary' (0x1e5c39bc0)

{"msgType":"UPDATE_S","macAddress":"2F-01-01-01-01-01","deviceName":"vMining","deviceType":"vSensor","groupId":"vMiningYo","param”:”truckDetail","value":"[{'TruckNo':1,'Status':'Moving to crusher','Speed':0,'CheckPointNumber':16,'CurrentLoad':346,'TirePressureIssueWheelNumber':0,'TirePressure':6,'Longitude':36.835072,'Latitude':-109.059769,'Altitude':1.0}]","valueDimension":"JSON","topic":"in/vMiningYo_ios/vMINING","_MessageGateway_TimeMilliseconds":1557471205646,"_MessageGateway_TimeSeconds":1557471205,"_MessageGateway_TimeISO8601":"2019-05-10T06:53:25.646Z","_MessageGateway_MessageClientType":"WS","_MessageGateway_Topic":"in/vMiningYo_ios/vMINING"}
I'm getting above json data in string format and i'm parsing with JSONSerialization like below method. I can able to print value data in console but how can i access inside values based on keys.
func recieveMessage(json:String){
guard let data = json.data(using: .utf16),
let jsonData = try? JSONSerialization.jsonObject(with: data),
let jsonDict = jsonData as? [String: Any],
let param = jsonDict["param"] as? String else {
return
}
if param == "truckDetail"{
print("VALUE:: \(jsonDict["value"]!)")
let truckData = jsonDict["value"]! as! [String:Any]
print(truckData)
print(truckData["Status"])
// if let truckData = jsonDict["value"] as? [String: Any]{
// print(truckData)
// }
}
}
Printing in Console:
VALUE:: [{'TruckNo':1,'Status':'Moving to crusher','Speed':0,'CheckPointNumber':16,'CurrentLoad':346,'TirePressureIssueWheelNumber':0,'TirePressure':6,'Longitude':36.835072,'Latitude':-109.059769,'Altitude':1.0}]
How can I print Status value in this Dictionary? Thanks in advance.
There are two major issues:
The object for key value is a String (apparently JSON). That's what the error clearly says.
But actually it's not valid JSON because JSON strings must be wrapped in double quotes, single quotes are not supported.
You need two extra steps: Replace the single quotes with double quotes and deserialize the JSON string separately.
And the object is an array. Either use a loop or get an item by index
let json = """
{"msgType":"UPDATE_S","macAddress":"2F-01-01-01-01-01","deviceName":"vMining","deviceType":"vSensor","groupId":"vMiningYo","param":"truckDetail","value":"[{'TruckNo':1,'Status':'Moving to crusher','Speed':0,'CheckPointNumber':16,'CurrentLoad':346,'TirePressureIssueWheelNumber':0,'TirePressure':6,'Longitude':36.835072,'Latitude':-109.059769,'Altitude':1.0}]","valueDimension":"JSON","topic":"in/vMiningYo_ios/vMINING","_MessageGateway_TimeMilliseconds":1557471205646,"_MessageGateway_TimeSeconds":1557471205,"_MessageGateway_TimeISO8601":"2019-05-10T06:53:25.646Z","_MessageGateway_MessageClientType":"WS","_MessageGateway_Topic":"in/vMiningYo_ios/vMINING"}
"""
let data = Data(json.utf8)
do {
if let result = try JSONSerialization.jsonObject(with: data) as? [String:Any],
let param = result["param"] as? String {
if param == "truckDetail" {
let value = result["value"] as! String
let valueString = value.replacingOccurrences(of: "\'", with: "\"")
let valueData = Data(valueString.utf8)
if let valueResult = try JSONSerialization.jsonObject(with: valueData) as? [[String:Any]] {
for item in valueResult {
print(item["Status"] as? String ?? "n/a")
}
}
}
}
} catch { print(error)}
jsonDict["value"] is a json string not a dictionary
let truckStr = jsonDict["value"] as! String
let jsonDic = try! JSONSerialization.jsonObject(with:Data(truckStr.utf8)) as! [String: Any]
print(jsonDic["Status"])

Working with JSON data retrieving into Swift data types

I'm trying to get data from a URL. It was successful. I can download and convert to a dictionary[String : Any] but response is in nested loops. I don't to how to retrieve. Can someone suggest how to get text and value in the response?
func getDataFromUrl() {
let url = URL(string: "https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&departure_time=1408046331&origins=37.407585,-122.145287&destinations=37.482890,-122.150235")
let request = NSMutableURLRequest(url: url!)
let session = URLSession.shared
request.httpMethod = "GET"
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
do {
let jsonData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any]
let destinationAddress = jsonData!["destination_addresses"]
print("Destination address \(String(describing: destinationAddress!))")
let origin_addresses = jsonData!["origin_addresses"]
print("Origin_addresses \(String(describing: origin_addresses!))")
let rows = jsonData!["rows"]
print("Rows \(String(describing: rows!))")
// Here I want to print text and value.
} catch {
// handle error
}
})
dataTask.resume()
}
The above answers work, but in my opinion the more swiftier approach is to use Codable.
class MyResponseType:Codable {
let destination_addresses:String
let rows:[MyCustomRowData]
}
class MyCustomRowData:Codable {
let elements:[MyCustomElementsData]
}
class MyCustomElementsData:Codable {
// properties here
}
Doing this, parsing the json is done like this:
let response = try? JSONDecoder().decode(MyResponseType.self, from: data)
Where the data variable is just the retrieved Data object from the request.
Initially you have to set up some boilerplate code to replicate your expected data format, but working with it is really worth it (and it makes it highly testable).
When the decode succeeds you have a perfectly typed object, it can also have optionals. It just wont decode if fields are missing or of the wrong type (which is a good thing).
Here is the way you can parse text and Value from response:
do{
if let jsonData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any] {
if let destinationAddress = jsonData["destination_addresses"] as? [String] {
print(destinationAddress) //["1 Hacker Way, Menlo Park, CA 94025, USA"]
}
if let origin_addresses = jsonData["origin_addresses"] as? [String] {
print(origin_addresses) //["3251 Hillview Ave, Palo Alto, CA 94304, USA"]
}
if let rows = jsonData["rows"] as? [[String: AnyObject]] {
if rows.indices.contains(0) {
if let elements = rows[0]["elements"] as? [[String: AnyObject]] {
for element in elements {
if let duration = element["duration"] as? [String: AnyObject] {
let text = duration["text"] as? String ?? ""
print(text) //17 mins
let value = duration["value"] as? Int ?? 0
print(value) //1010
}
if let distance = element["distance"] as? [String: AnyObject] {
let text = distance["text"] as? String ?? ""
print(text) //7.2 mi
let value = distance["value"] as? Int ?? 0
print(value) //11555
}
}
}
}
}
}
}catch{ //error handle
}
Use this code:
let rows = jsonData["rows"] as! Array
let element = rows[0] as! Dictionary
let elementArray = element.value(forKey: "elements")
let distance = elementArray[0].value(forKey: "distance")
let text = distance.value(forKey: "text")
print(text)
let value = distance.value(forKey: "value")
print(value)

How to parse a api for swift 3?

Have been researching on the parsing for quite a bit. With plethora of information avilable for JSON nothing seems to explain how to do in a sensible way to extract information with swift 3.
This is what got so far
func getBookDetails() {
let scriptUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:9781451648546" .
let myurl = URL(string:scriptUrl)
let request = NSMutableURLRequest(url: myurl!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: myurl! ) { (data, response, error) in
if error != nil{
print("THIS ERROR",error!)
return
} else{
if let mydata = data{
do{
let myJson = try (JSONSerialization.jsonObject(with: mydata, options: JSONSerialization.ReadingOptions.mutableContainers)) as AnyObject
// print("this is the MY JSON",myJson) ---> prints out the json
if let dictonary = myJson["items"] as AnyObject? {
print("the DICTONARY",dictonary) // ----> OUTPUT
if let dictonaryAA = dictonary["accessInfo"] as AnyObject? {
print("the accessInfo",dictonaryAA)
}
}
} catch{
print("this is the in CATCH")
}
} //data
}
}
task.resume()
}
}
OUTPUT :
the DICTONARY (
{
accessInfo = {
accessViewStatus = SAMPLE;
country = US;
=============
RELEVANT DATA as in https://www.googleapis.com/books/v1/volumes?
q=isbn:9781451648546"
==========================
title = "Steve Jobs";
};
}
)
Just need to parse through the json data to get the name, author and title of the book with reference to isbn.
Know there should be a better way to do things that is easily understandable to someone new into the language
You can parse the api in two ways
Using URLSession:
let rawDataStr: NSString = "data={\"mobile\":\"9420....6\",\"password\":\"56147180..1\",\"page_no\":\"1\"}"
self.parsePostAPIWithParam(apiName: "get_posts", paramStr: rawDataStr){ ResDictionary in
// let statusVal = ResDictionary["status"] as? String
self.postsDict = (ResDictionary["posts"] as! NSArray!) as! [Any]
print("\n posts count:",self.postsDict.count)
}
func parsePostAPIWithParam(apiName:NSString, paramStr:NSString,callback: #escaping ((NSDictionary) -> ())) {
var convertedJsonDictResponse:NSDictionary!
let dataStr: NSString = paramStr
let postData = NSMutableData(data: dataStr.data(using: String.Encoding.utf8.rawValue)!)
let request = NSMutableURLRequest(url: NSURL(string: "http://13.12..205.248/get_posts/")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = nil
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error as Any)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse as Any)
do{
if let convertedJsonIntoDict = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
convertedJsonDictResponse = convertedJsonIntoDict.object(forKey: apiName) as? NSDictionary
// callback for response
callback(convertedJsonDictResponse)
}
} catch let error as NSError {
print(error)
}
}
Using Alamofire
func AlamofirePOSTRequest() {
let urlString = "http://13.12..205.../get_posts/"
let para = ["data": "{\"mobile\":\"9420....6\",\"password\":\"56147180..1\",\"page_no\":\"1\"}"]
Alamofire.request(urlString, method: .post, parameters: para , headers: nil).responseJSON {
response in
switch response.result {
case .success:
print("response: ",response)
let swiftyJsonVar = JSON(response.result.value!)
if let resData = swiftyJsonVar["posts"].arrayObject {
self.postsDict = resData as! [[String:AnyObject]]
}
print("\n \n alomafire swiftyJsonVar: ",swiftyJsonVar)
break
case .failure(let error):
print(error)
}
}
}
})
dataTask.resume()
}
First of all, all JSON types are value types in Swift 3 so the most unspecified type is Any, not AnyObject.
Second of all, there are only two collection types in the JSON type set, dictionary ([String:Any]) and array ([Any], but in most cases [[String:Any]]). It's never just Any nor AnyObject.
Third of all, the given JSON does not contain a key name.
For convenience let's use a type alias for a JSON dictionary:
typealias JSONDictionary = [String:Any]
The root object is a dictionary, in the dictionary there is an array of dictionaries for key items. And pass no options, .mutableContainers is nonsense in Swift.
guard let myJson = try JSONSerialization.jsonObject(with: mydata) as? JSONDictionary,
let items = myJson["items"] as? [JSONDictionary] else { return }
Iterate through the array and extract the values for title and authors which is an array by the way. Both values are in another dictionary for key volumeInfo.
for item in items {
if let volumeInfo = item["volumeInfo"] as? JSONDictionary {
let title = volumeInfo["title"] as? String
let authors = volumeInfo["authors"] as? [String]
print(title ?? "no title", authors ?? "no authors")
The ISBN information is in an array for key industryIdentifiers
if let industryIdentifiers = volumeInfo["industryIdentifiers"] as? [JSONDictionary] {
for identifier in industryIdentifiers {
let type = identifier["type"] as! String
let isbn = identifier["identifier"] as! String
print(type, isbn)
}
}
}
}
You are doing wrong in this line
if let dictonaryAA = dictonary["accessInfo"] as AnyObject?
because dictonary here is an array not dictionary. It is array of dictionaries. So as to get first object from that array first use dictonary[0], then use accessInfo key from this.
I am attaching the code for your do block
do{
let myJson = try (JSONSerialization.jsonObject(with: mydata, options: JSONSerialization.ReadingOptions.mutableContainers)) as AnyObject
// print("this is the MY JSON",myJson) ---> prints out the json
if let array = myJson["items"] as AnyObject? {
print("the array",array) // ----> OUTPUT
let dict = array.object(at: 0) as AnyObject//Master Json
let accessInf = dict.object(forKey: "accessInfo") //Your access info json
print("the accessInfo",accessInf)
}
}
Hope this helps you.

Get data out of array of dictionaries from JSON response

I am trying to get data out of an array of nested dictionaries which is the result of a JSON response.
func fetchData() {
let urlString = "https://global.api.pvp.net/api/lol/static-data/na/v1.2/champion?api_key=\(self.apiKey)"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error ?? "ERROR!")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
print(parsedData)
print(parsedData["type"] ?? "")
print(parsedData["version"] ?? "")
print(type(of: parsedData))
print(type(of: parsedData["data"]!))
//ERROR HERE.
//"Cannot subscript a value of type '[String, Any]' with an index of type '(String, (String, Any).Type)'
let innerItem = parsedData["Aatrox", (String, Any)]
} catch let error as NSError {
print(error)
}
}
}.resume()
Here is what is printed:
["type": champion, "version": 7.4.3, "data": {
Aatrox = {
id = 266;
key = Aatrox;
name = Aatrox;
title = "the Darkin Blade";
};
Ahri = {
id = 103;
key = Ahri;
name = Ahri;
title = "the Nine-Tailed Fox";
};
Akali = {
id = 84;
key = Akali;
name = Akali;
title = "the Fist of Shadow";
};
Alistar = {
id = 12;
key = Alistar;
name = Alistar;
title = "the Minotaur";
};
}]
champion
7.4.3
Dictionary<String, Any>
__NSDictionaryI
I am trying to get the "id" for "Aatrox".
How would i go about doing this?
is it because the type of parsedData["data"]! is __NSDictionaryI??
Thanks for the help.
First you need to extract data Dictionary then access other dictionary.
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
if let passDic = parsedData["data"] as? [String:Any],
let innerItem = passDic["Aatrox"] as? [String: Any] {
print(innerItem)
}
} catch {
print(error)
}
Seems like it should be let innerItem: [String: Any] = parsedData["Aatrox"]
let data : [string:Any] = parsedData.value(key: "data")
let aatrox : [string:Any] = data["Aatrox"]
print("**id**\(aatrox["id"])")
this thing work for me..

Swift, Json Dictionary<String, AnyObject> send value to label

I have a json who returns me a Dictionary. I can have values inside but I want to use one of this value to put in a label. I have no error but my label inside my view doesn't change.
Here code:
let url = "http://localhost:8888/connexion/"+login!+"/"+password!
let urlString = url.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let request = NSURLRequest(URL: NSURL(string: urlString)!)
let alreadyTask = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) in
guard let data = data, let _ = response where error == nil else {
print("error")
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as! Dictionary<String, AnyObject>
print(json)
print(json["3"] as! String)
print(json["4"] as! String)
print(json["5"] as! String)
self.race_label.text = json["3"] as! String
pa_label.text = json["5"] as! String
name_label.text = json["4"] as! String
} catch let error as NSError {
print("json error: \(error.localizedDescription)")
}
})
alreadyTask.resume()
}
And Here log:
["2": blabla12, "1": c#gmail.com, "3": orc, "4": test, "success": 1, "0": 5, "5": 100]
orc
test
100
So, I don't know why it doesn't work. Thank you !
You have to do UI related stuff on the main queue. dataTaskWithRequests calls it's completion handler on a background queue.
To assure your calls are made on the main queue replace:
self.race_label.text = json["3"] as! String
pa_label.text = json["5"] as! String
name_label.text = json["4"] as! String
with:
Swift 2:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.race_label.text = json["3"] as! String
self.pa_label.text = json["5"] as! String
self.name_label.text = json["4"] as! String
})
Swift3:
DispatchQueue.main.async {
self.race_label.text = json["3"] as! String
self.pa_label.text = json["5"] as! String
self.name_label.text = json["4"] as! String
}

Resources