How To Handle json null values in Swift? - json

My application is crashing while pursing null json
I was tying to solve this issue since long time
you are the best who can assist me with this issue
func downloadJsonWithURLJB() {
let url=URL(string:"http://ccm-hotels.com/RixosJBR/IOS/api/Con4s.php")
do {
let allContactsData = try Data(contentsOf: url!)
let allContacts = try JSONSerialization.jsonObject(with: allContactsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : AnyObject]
if let arrJSON = allContacts["Con+4"] as? [[String : Any]] {
for index in 0...arrJSON.count-1 {
let aObject = arrJSON[index] as! [String : AnyObject]
Con4Array.append(Con4s(DateCon4: (aObject["Date"] as? String)!, TimeCon4: (aObject["Time"] as? String)!,GuestNameCon4: (aObject["GuestName"] as? String)!, RoomCon4: (aObject["Room"] as? String)!, LimoCoCon4: (aObject["LimoCo"] as? String)!, DriverCon4: (aObject["Driver"] as? String)!, VehicleCon4: (aObject["Vehicle"] as? String)!, FlightCon4: (aObject["Flight"] as? String)!, PickUpCon4: (aObject["PickUp"] as? String)!, DropToCon4: (aObject["DropTo"] as? String)!, PaxCon4: (aObject["Pax"] as? String)!,TotalCon4: (aObject["Total"] as? String)!, CompleteCon4: (aObject["complete"] as? String)!))
}
}
DispatchQueue.main.async {
self.tableCon4.reloadData()
}
}
catch {}}

The exception
Can't form Range with upperBound < lowerBound
occurs due to the ugly objective-c-ish for loop. If the array is empty the upper bound is -1 which is less than the lower bound.
In Swift never use index based for loops if the index is actually not needed.
Replace
for index in 0...arrJSON.count-1 {
let aObject = arrJSON[index] as! [String : AnyObject]
with
for aObject in arrJSON {
If you do need the index use the enumerated() syntax
for (index, aObject) in arrJSON.enumerated() {
or the safe half-open range operator
for index in 0..<arrJSON.count {
Other bad practices:
Never load data from a remote URL with synchronous API Data(contentsOf not even on a background thread.
A JSON dictionary in Swift 3+ is [String:Any], never [String:AnyObject].
Variable and property names start with a lowercase letter.
.allowFragments is pointless if the expected type is a collection type.

Related

Not able to decode a json answer in swift

I try to decode a json answer from a camera. But the camera is answering keys with null (this depends on the camera type).
The result string looks like that:
let string = "{\"id\":1,\"result\":[{\"names\":[\"getAvailableApiList\",\"getShootMode\",\"getSupportedShootMode\",\"getAvailableShootMode\",\"setFlashMode\",\"getFlashMode\",\"getSupportedFlashMode\",\"getAvailableFlashMode\",\"setSelfTimer\",\"getSelfTimer\",\"getSupportedSelfTimer\",\"getAvailableSelfTimer\",\"getSupportedMovieQuality\",\"startLiveview\",\"stopLiveview\",\"actTakePicture\",\"startMovieRec\",\"stopMovieRec\",\"awaitTakePicture\",\"getExposureMode\",\"getSupportedExposureMode\",\"getAvailableExposureMode\",\"setExposureCompensation\",\"getExposureCompensation\",\"getSupportedExposureCompensation\",\"getAvailableExposureCompensation\",\"setFNumber\",\"getFNumber\",\"getSupportedFNumber\",\"getAvailableFNumber\",\"setWhiteBalance\",\"getWhiteBalance\",\"getSupportedWhiteBalance\",\"getAvailableWhiteBalance\",\"getShutterSpeed\",\"getSupportedShutterSpeed\",\"getAvailableShutterSpeed\",\"setIsoSpeedRate\",\"getIsoSpeedRate\",\"getSupportedIsoSpeedRate\",\"getAvailableIsoSpeedRate\",\"actHalfPressShutter\",\"cancelHalfPressShutter\",\"getSupportedProgramShift\",\"getSupportedMovieFileFormat\",\"setContShootingMode\",\"getContShootingMode\",\"getSupportedContShootingMode\",\"getAvailableContShootingMode\",\"setWirelessFlashSetting\",\"getWirelessFlashSetting\",\"getSupportedWirelessFlashSetting\",\"getAvailableWirelessFlashSetting\",\"getApplicationInfo\",\"getEvent\",\"getTemporarilyUnavailableApiList\"],\"type\":\"availableApiList\"},{\"cameraStatus\":\"IDLE\",\"type\":\"cameraStatus\"},null,{\"liveviewStatus\":true,\"type\":\"liveviewStatus\"},null,[],[{\"continuousError\":\"Overheating Warning\",\"isContinued\":false,\"type\":\"continuousError\"},{\"continuousError\":\"Remote Control Not Currently Available\",\"isContinued\":false,\"type\":\"continuousdfError\"}],null,null,null,[],null,null,null,null,null,null,null,{\"currentExposureMode\":\"Aperture\",\"exposureModeCandidates\":[],\"type\":\"exposureMode\"},null,{\"currentSelfTimer\":0,\"selfTimerCandidates\":[0,2,5,10],\"type\":\"selfTimer\"},{\"currentShootMode\":\"still\",\"shootModeCandidates\":[\"still\",\"movie\",\"slow and quick\"],\"type\":\"shootMode\"},null,null,null,{\"currentExposureCompensation\":0,\"maxExposureCompensation\":15,\"minExposureCompensation\":-15,\"stepIndexOfExposureCompensation\":1,\"type\":\"exposureCompensation\"},{\"currentFlashMode\":\"on\",\"flashModeCandidates\":[\"on\",\"rearSync\",\"slowSync\"],\"type\":\"flashMode\"},{\"currentFNumber\":\"3.5\",\"fNumberCandidates\":[\"3.5\",\"4.0\",\"4.5\",\"5.0\",\"5.6\",\"6.3\",\"7.1\",\"8.0\",\"9.0\",\"10\",\"11\",\"13\",\"14\",\"16\",\"18\",\"20\",\"22\"],\"type\":\"fNumber\"},null,{\"currentIsoSpeedRate\":\"250\",\"isoSpeedRateCandidates\":[\"AUTO\",\"50\",\"64\",\"80\",\"100\",\"125\",\"160\",\"200\",\"250\",\"320\",\"400\",\"500\",\"640\",\"800\",\"1000\",\"1250\",\"1600\",\"2000\",\"2500\",\"3200\",\"4000\",\"5000\",\"6400\",\"8000\",\"10000\",\"12800\",\"16000\",\"20000\",\"25600\",\"32000\",\"40000\",\"51200\",\"64000\",\"80000\",\"102400\",\"128000\",\"160000\",\"204800\"],\"type\":\"isoSpeedRate\"},null,null,{\"currentShutterSpeed\":\"1/100\",\"shutterSpeedCandidates\":[],\"type\":\"shutterSpeed\"},{\"checkAvailability\":true,\"currentColorTemperature\":-1,\"currentWhiteBalanceMode\":\"Auto WB\",\"type\":\"whiteBalance\"},null,{\"focusStatus\":\"Not Focusing\",\"type\":\"focusStatus\"},null,null,{\"candidate\":[\"Single\",\"Continuous\"],\"contShootingMode\":\"Single\",\"type\":\"contShootingMode\"},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}"
What is the best way to decode a json answer like that?
Use JSONSerialization and cast result to a dictionary of type [String: Any]
let data = string.data(using: .utf8)!
do {
let result = try JSONSerialization.jsonObject(with: data) as! [String: Any]
//extract info from result
if let array = result["result"] as? [Any] {
let item = array.filter( {
if let dict = $0 as? [String: Any] {
return dict.contains(where: { $0.key == "currentShootMode"})
}
return false
})
print(item)
}
} catch {
print(error)
}
to extract info from the item variable you need to cast again
if let mode = item.first as? [String: Any], let value = mode["currentShootMode"] {
print(value)
}

Cast from '(key: Any, value: Any)' to unrelated type '[String : Any]' always fails

I am working on a project and have reached a stage where I want to display data from a web service in a table view. My json data is in dictionary format and I am taking loop for fetching the data from dictionary by using key but it's getting the warning. So, please help me for this type of warning.
My loop is:-
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: []) as! NSDictionary
for eachFetchedRestaurant in fetchedData {
let eachRestaurant = eachFetchedRestaurant as! [String : Any]
let restaurantName = eachRestaurant["restName"] as! String
let restaurantImage = eachRestaurant["restaurant_image"] as! String
self.fetchedRestaurant.append(Restaurants(restaurantName: restaurantName, restaurantImage: restaurantImage))
}
print(self.fetchedRestaurant)
Getting warning on this line:-
let eachRestaurant = eachFetchedRestaurant as! [String : Any]
Cast from '(key: Any, value: Any)' to unrelated type '[String : Any]' always fails
Thanks in advance for helping!!!
Since JSON response is array of (key, value) format, so your fetch data should be of [[String:Any]] format. Here is the updated code.
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [[String:Any]]
for (key,eachFetchedRestaurant) in fetchedData {
let eachRestaurant = eachFetchedRestaurant as! [String : String]
let restaurantName = eachRestaurant["restName"] as! String
let restaurantImage = eachRestaurant["restaurant_image"] as! String
self.fetchedRestaurant.append(Restaurants(restaurantName: restaurantName, restaurantImage: restaurantImage))
}
print(self.fetchedRestaurant)
First of all don't use NSDictionary and NSArray in Swift, use native types.
You are applying array related enumeration API to a dictionary. The dictionary related API is
for (key, eachFetchedRestaurant) in fetchedData
You've got two options:
fetchedData is indeed expected to be dictionary then cast
let fetchedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
and use the mentioned enumeration API
(the more likely one) fetchedData is expected to be an array then you have to cast
let fetchedData = try JSONSerialization.jsonObject(with: data!) as! [[String:Any]]

Swift 3 JSON Bug?

I can't seem to compile my code no matter how much I fiddle with it. I think I need another perspective.
At "let image = data["images"] as! [String : AnyObject]"
xcode keeps telling me "Cannot subscript a value of type '[[String : AnyObject]]' with an index of type 'String'"
func retreiveInstagramMedia() {
let token = user?.token
let urlString = "https://api.instagram.com/v1/users/self/media/recent/?access_token=\(token!)"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: {
(data, response, error) in
if(error != nil){
print("error")
}else{
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String : AnyObject]
if let data = json["data"] as? [[String : AnyObject]] {
let image = data["images"] as! [String : AnyObject]
let standardResolution = image["standard_resolution"] as! [String : AnyObject]
let url = standardResolution["url"] as! String
print(url)
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}catch let error as NSError{
print(error)
}
}
}).resume()
}
As Martin said, your data is an array of dictionaries. So iterate through those dictionaries:
if let arrayOfData = json["data"] as? [[String : Any]] {
for individualData in arrayOfData {
if let image = individualData["images"] as? [String : Any],
let standardResolution = image["standard_resolution"] as? [String : Any],
let url = standardResolution["url"] as? String {
// now do something with URL
print(url)
}
}
}
I'd suggest you avoid using ! to force cast/unwrap. When dealing with data from remote sources, you should more gracefully handle situations where one of these subscripts fail to retrieve what you expect they would.
First of all in Swift 3 a JSON dictionary is [String:Any]
Your mistake (developer bug) is to use the array data as the index variable of the loop.
Replace the underscore with anItem and get the image with anItem["images"]
let json = try JSONSerialization.jsonObject(with: data!) as! [String : Any]
if let data = json["data"] as? [[String : Any]] {
for anItem in data {
let image = anItem["images"] as! [String : Any]
let standardResolution = image["standard_resolution"] as! [String : Any]
let url = standardResolution["url"] as! String
print(url)
}
}
Note: The key images implies multiple items so the value could be also an array.

"Expression Implicity Coerced from 'String?' to Any" JSON Swift 3

Hi I have the below JSON code I would like to parse
"data":{
"date":"November 30th, 2016",
"personality":"Larry",
"comment":"Saw the homie today"
},
I'm doing this in my viewDidLoad
let url=URL(string:"http://IP-ADDRESS/info.php")
do {
let allNotificationsData = try Data(contentsOf: url!)
let allNotication = try JSONSerialization.jsonObject(with: allNotificationsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : AnyObject]
if let arrJSON = allNotication["notifications"] {
for index in 0...arrJSON.count-1 {
let aObject = arrJSON[index] as? [String: AnyObject]
//let name = aObject?["data"]!
if let jsonResponse = aObject,
let info = jsonResponse["data"] {
// Makes sense to check if count > 0 if you're not sure, but...
let transaction_id: String? = info["personality"] as? String
print(transaction_id)
// Do whatever else you need here
}
Which seems to be fine but console returns below. Not sure while "nil" but I just want it show me "date" in the JSON file itself only in the console. Eventually I'll need it to catch an array of dates, not sure how I'll do that but I'm working on it. Let me know if you know what I'm doing wrong. It has to be something with optional.
Assuming the parent object of data is an array (your code suggests that) you can get all data objects in an array with:
do {
let allNotificationsData = try Data(contentsOf: url!)
let allNotification = try JSONSerialization.jsonObject(with: allNotificationsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String:Any]
if let arrJSON = allNotification["notifications"] as? [[String:Any]] {
let infoArray = arrJSON.flatMap { $0["data"] }
}
...
}
The benefit of flatMap is it ignores nil values.
If you want to access the comment value of the first item in the array write
let comment = (infoArray[0] as! [String:Any])["comment"] as! String

Strange behaviour of optionals in Swift 3

I have experienced a strange behaviour when parsing JSON data using Swift 3.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: []) as! NSDictionary
let items:[AnyObject] = (json["items"] as? [AnyObject])!
for item in items {
let id:String = item["id"] as! String
print("ID: \(id)")
let info = item["volumeInfo"] as AnyObject
print(info)
let title = info["title"]
print(title)
}
} catch {
print("error thrown")
}
This produces the following output. Notice that info is an optional but if I try to unwrap it it states it is not an optional! The script crashes on let title = info["title"] As a result I can't access the title key. This behaviour has changed since Swift 2.
ID: lbvUD6LUyV8C
Optional({
publishedDate = 2002;
publisher = "Sams Publishing";
title = "Java Deployment with JNLP and WebStart";
})
You can do something like:
do {
let json = try JSONSerialization.jsonObject(with: data!) as! [String: Any]
let items = json["items"] as! [[String: Any]]
for item in items {
let id = item["id"] as! String
let info = item["volumeInfo"] as! [String: Any]
let title = info["title"] as! String
print(id)
print(info)
print(title)
}
} catch {
print("error thrown: \(error)")
}
I might suggest excising the code of the ! forced unwrapping (if the JSON was not in the form you expected, do you really want this to crash?), but hopefully this illustrates the basic idea.
The runtime type of info is Optional<Something>, but the compile time type (as you explicitly cast it) is AnyObject. How is the compiler supposed to know that the AnyObject will happen to be an Optional<Something> unless you tell it (in the form of a cast)?
Try:
let info = item["volumeInfo"] as AnyObject?
Now the compiler knows it's an Optional<AnyObject>, so you can unwrap it.
In Swift 3 the compiler must know the types of all subscripted objects if it's an array or dictionary. AnyObject – which has been changed to Any in Swift 3 – is not sufficient.
Since you know that the value for key volumeInfo is a dictionary cast it accordingly preferably using optional bindings
let info = item["volumeInfo"] as? [String:Any] {
print(info)
let title = info["title"] as! String
print(title)
}
This should do:
guard let json = try? JSONSerialization.jsonObject(with: data!, options: []) as! [String: AnyObject],
let items = json["items"] as! Array<AnyObject>? else {
return
}
for item in items {
let id = item["id"] as! String
if let info = item["volumeInfo"] as? [String: AnyObject] {
let title = info["title"] as! String
} else {
// do something
}
}