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)
}
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]]
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.
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
}
}