JSON String to NSDictionary with Swift - ios

I am trying to create a dictionary from data that is held in a server, I receive the data but I cannot convert the data to an NSDictionary, I believe it is held in an NSData Object
let JSONDictionary: Dictionary = NSJSONSerialization.JSONObjectWithData(JSONData!, options: nil, error: &error) as NSDictionary
This line of code is the one giving me the problem, it throws a BAD_EXEC_INSTRUCTION.
MY Question: How can I turn a JSON into an NSDictionary?

Your code does not do any error handling. But it can (and if this data comes from a web service, will) fail in multiple ways.
You have to make sure that your data object actually exists
You have to make sure that the data object can be converted to JSON
You have to make sure that the JSON actually contains a Dictionary
You should use Swifts conditional cast and it's optional binding capabilities.
The optional binding if let JSONData = JSONData checks that JSONData is not nil. The force unwrap (JSONData!) you use might crash if no data could be received.
The optional binding if let json = NSJSONSerialization.JSONObjectWithData checks if the data could be converted to a JSON object. The conditional cast as? NSDictionary checks if the JSON object is actually a dictionary. You currently don't use these checks, you cast the objects as NSDictionary. Which will crash, if the object is not valid json, or if its not a dictionary.
I would recommend something like this:
var error: NSError?
if let JSONData = JSONData { // Check 1
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) { // Check 2
if let jsonDictionary = json as? NSDictionary { // Check 3
println("Dictionary received")
}
else {
if let jsonString = NSString(data: JSONData, encoding: NSUTF8StringEncoding) {
println("JSON String: \n\n \(jsonString)")
}
fatalError("JSON does not contain a dictionary \(json)")
}
}
else {
fatalError("Can't parse JSON \(error)")
}
}
else {
fatalError("JSONData is nil")
}
You could merge check 2 and 3 into one line and check if NSJSONSerialization can create a NSDictionary directly:
var error: NSError?
if let JSONData = JSONData { // Check 1.
if let JSONDictionary = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) as? NSDictionary { // Check 2. and 3.
println("Dictionary received")
}
else {
if let jsonString = NSString(data: JSONData, encoding: NSUTF8StringEncoding) {
println("JSON: \n\n \(jsonString)")
}
fatalError("Can't parse JSON \(error)")
}
}
else {
fatalError("JSONData is nil")
}
Make sure to replace fatalError with appropriate error handling in your production code

Update for Swift 2.
Now you must use it inside a try catch block.
do {
let responseObject = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String:AnyObject]
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}

Here jsonResult will give you the response in NSDictionary:
let url = NSURL(string: path)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
})
task.resume()

Related

How to get array response in swift iOS Request

I send request like this:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
// check for fundamental networking error
print("error=\(String(describing: error))")
completion(nil)
return
}
print("********_Respone status code is: ",(response as! HTTPURLResponse).statusCode)
print("********_Respone url code is: ",response?.url as Any )
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
let res:HTTPURLResponse = response as! HTTPURLResponse
print(json)// yessssssssss goooood
} catch {
completion(nil)
return
}
}
task.resume()
it's working correctly when response is a Dictionary, but when my response is an array show this error:
Could not cast value of type '__NSArrayI' to 'NSDictionary'
Please help me to fix this.
Deserialize the JSON once and omit the options, Array and Dictionary aren't fragmented. Then optional bind the result.
do {
let json = try JSONSerialization.jsonObject(with: data)
if let jsonArray = json as? [[String:Any]] {
print("json is array", jsonArray)
} else if let jsonDictionary = json as? [String:Any] {
print("json is dictionary", jsonDictionary)
} else {
print("This should never be displayed")
}
} ...
If the result is supposed to be only Array or Dictionary then you can force unwrap the result to dictionary and remove the last else clause
do {
let json = try JSONSerialization.jsonObject(with: data)
if let jsonArray = json as? [[String:Any]] {
print("json is array", jsonArray)
} else {
let jsonDictionary = json as! [String:Any]
}
} ...

NSJSONSerialization Error : Why am I not able to catch the error in try catch block? swift 3.0

I am calling REST API to get a response from the server and also testing for bad data type to caught in try catch block nut app still crashes with below error:
Could not cast value of type '__NSArrayM' (0x10575ee00) to 'NSDictionary' (0x10575f2d8).
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error!)
return
}
do {
let responseObject = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! Dictionary<String,String>
print(responseObject)
} catch let jsonError {
print(jsonError)
}
}
dataTask.resume()
For this, I also found the solution that I should check data with JSON format first then use JSONSerialization.
if JSONSerialization.isValidJSONObject(data){
responseObject = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! Dictionary<String,String>
}
else {
print("Error")
}
I want to know that if type cast value error not caught by the try-catch block then what is a use of try-catch block in network call. I can simply check for the JSON format and the pass the response object.
What is a good programming practice?
NOTE : Error is not an issue I just don't want my should crash an app when data type changes form dictionary to array or other than specified type.
This is not about valid JSON, you are getting this crash because your JSON at root is Array not Dictionary and you are trying to cast the result of jsonObject(with:) to Dictionary that is the reason you are getting this crash also there is no need to specify mutableContainers with Swift.
do {
let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as! [[String:Any]]
print(responseObject)
} catch let jsonError {
print(jsonError)
}
Edit: If you don't know your JSON time then you can use if let like this way.
do {
let responseObject = try JSONSerialization.jsonObject(with: data, options: [])
if let responseArray = responseObject as? [[String:Any]] {
//Access array
}
else if let responseDictionary = responseObject as? [String:Any] {
//Access dictionary
}
print(responseObject)
} catch let jsonError {
print(jsonError)
}

Swift How to update (modify, Delete, Add) entries of JSON

Hello i need some help here, I'm making an IOS app that gets data from a JSON API and then showing the results on a Table , when i tap on a result from the table it goes to a second view controller where i'm showing the details. What I want to do is to update the info I'm showing on the details, delete entries from the JSON by deleting them from the table itself, and add a new entry to be saved on the JSON.
This is the JSON structure:
{
_id: "57eec6c9dfc2fb03005c0dd0",
ssid: "nonummy",
password: "accumsan",
lat: 29.39293,
lon: 115.71771,
summary: "curae nulla dapibus dolor vel est donec odio justo sollicitudin ut",
__v: 0,
likes: 1,
unlikes: 0,
bssid: "EF:CD:AB:56:34:12"
},
I want to be able to update the SSID, Password and Summary.
this is the code I'm using to get the Result from the JSON and is working good
Code:
let url = URL(string:"https://fierce-peak-97303.herokuapp.com/api/wifi")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
}else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
//print(jsonResult))
for item in(jsonResult as? NSArray)! {
let ssid = (item as? NSDictionary)?["ssid"] as? NSString
//print(ssid)
}
self.tableData = jsonResult as! NSArray
DispatchQueue.main.sync(execute: {
self.table.reloadData()
})
}catch {
print("No Json Result Was Found")
}
}
}
}
task.resume()
For example if I click on one line of the table I want to be able to update password.
I managed to do it like this : all formatted for swift 3
//declare parameter as a dictionary which contains string as key and value combination.
let parameters = ["ssid": newSSID.text!,"password": newPass.text!,"lat": newLat.text!,"lon": newLon.text!,"summary": newSum.text!] as Dictionary<String, String>
//create the url with NSURL
let url = URL(string: "https://fierce-peak-97303.herokuapp.com/api/wifi")
//create the session object
let session = URLSession.shared
//now create the NSMutableRequest object using the url object
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
var err : NSError?
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
}catch{
print("error")
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
let strData = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Body: \(strData)")
var err: NSError?
do {
var json = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? NSDictionary
}catch{
print("JSON error")
}
// Did the JSONObjectWithData constructor return an error? If so, log the error to the console
if(err != nil) {
print(err!.localizedDescription)
let jsonStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Error could not parse JSON: '\(jsonStr)'")
}
else {
// The JSONObjectWithData constructor didn't return an error. But, we should still
// check and make sure that json has a value using optional binding.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? NSDictionary
if let parseJSON = json {
// Okay, the parsedJSON is here, let's get the value for 'success' out of it
let success = parseJSON["success"] as? Int
print("Success: \(success)")
}
else {
// Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
let jsonStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Error could not parse JSON: \(jsonStr)")
}
}catch{
print("JSON error")
}
}
You can get the contents of the json file as whatever data structure you want, then overwrite the values that you want to change and finally overwrite the original file when you want to save your changes:
So you're already getting the JSON as an array:
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
let array = (jsonResult as? NSArray)!
So now you can just change the values in array, when you're done changing everything you want then you can overwrite the json file: (swift 3.0)
var jsonData: NSData!
// serialize json dataa
do
{
jsonData = try JSONSerialization.dataWithJSONObject(array, options: NSJSONWritingOptions())
let jsonString = String(data: jsonData as Data, encoding: String.Encoding.utf8)
print(jsonString)
}
catch let error as NSError
{
print("Array to JSON conversion failed: \(error.localizedDescription)")
}
// overwrite the contents of the original file.
do
{
let file = try FileHandle(forWritingToURL: url!)
file.writeData(jsonData)
print("JSON data was written to the file successfully!")
}
catch let error as NSError
{
print("Couldn't write to file: \(error.localizedDescription)")
}
I'm not sure exactly how writing to a server works, but since it's a URL i think it should act the same way.
Update:
So your original array is:
let array = (jsonResult as? NSArray)!
You can cast it to an array of dictionaries like so
var dictionaries : [[String: NSObject]] = array as? [[String: NSObject]]
and you get ssid by doing:
int i = 0;
for (dictionary in dictionaries)
{
let ssid = dictionary["ssid"]
let newSSID = ssid + "something I want to edit my values with"
// now we have our new value, so we update the array
dictionaries[i]["ssid"] = newSSID
i += 1
}
now after this we just overwrite the original file with the new dictionaries object, which I wrote out above.

Swift: Extra argument 'error' in call

I'm currently developing my first iOS app using Swift 2.0 and Xcode Beta 2. It reads an external JSON and generates a list in a table view with the data. However, I'm getting a strange little error that I can't seem to fix:
Extra argument 'error' in call
Here is a snippet of my code:
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
print("Task completed")
if(error != nil){
print(error!.localizedDescription)
}
var err: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary{
if(err != nil){
print("JSON Error \(err!.localizedDescription)")
}
if let results: NSArray = jsonResult["results"] as? NSArray{
dispatch_async(dispatch_get_main_queue(), {
self.tableData = results
self.appsTableView!.reloadData()
})
}
}
})
The error is thrown at this line:
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary{
Can someone please tell me what I'm doing wrong here?
With Swift 2, the signature for NSJSONSerialization has changed, to conform to the new error handling system.
Here's an example of how to use it:
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary {
print(jsonResult)
}
} catch let error as NSError {
print(error.localizedDescription)
}
With Swift 3, the name of NSJSONSerialization and its methods have changed, according to the Swift API Design Guidelines.
Here's the same example:
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? [String:AnyObject] {
print(jsonResult)
}
} catch let error as NSError {
print(error.localizedDescription)
}
Things have changed in Swift 2, methods that accepted an error parameter were transformed into methods that throw that error instead of returning it via an inout parameter. By looking at the Apple documentation:
HANDLING ERRORS IN SWIFT:
In Swift, this method returns a nonoptional result and is marked with the throws keyword to indicate that it throws an error in cases of failure.
You call this method in a try expression and handle any errors in the catch clauses of a do statement, as described in Error Handling in The Swift Programming Language (Swift 2.1) and Error Handling in Using Swift with Cocoa and Objective-C (Swift 2.1).
The shortest solution would be to use try? which returns nil if an error occurs:
let message = try? NSJSONSerialization.JSONObjectWithData(receivedData, options:.AllowFragments)
if let dict = message as? NSDictionary {
// ... process the data
}
If you're also interested into the error, you can use a do/catch:
do {
let message = try NSJSONSerialization.JSONObjectWithData(receivedData, options:.AllowFragments)
if let dict = message as? NSDictionary {
// ... process the data
}
} catch let error as NSError {
print("An error occurred: \(error)")
}
This has been changed in Swift 3.0.
do{
if let responseObj = try JSONSerialization.jsonObject(with: results, options: .allowFragments) as? NSDictionary{
if JSONSerialization.isValidJSONObject(responseObj){
//Do your stuff here
}
else{
//Handle error
}
}
else{
//Do your stuff here
}
}
catch let error as NSError {
print("An error occurred: \(error)") }

Why is my code crashing when I run it in Thread 6: NSOperationQueue?

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.forecast.io/forecast/MYKEYHERE/")
let session = NSURLSession.sharedSession()
let task: NSURLSessionDownloadTask = session.downloadTaskWithURL(url!, completionHandler: { (location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
if error == nil {
let data = NSData(contentsOfURL: location)
let json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as! NSDictionary!
println(json)
}
})
task.resume()
}
This is code for a download task to a weather API. Just wondering why I am getting the error:
Thread 6: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP, subcode=0x0).
Thanks a lot.
You're getting this error because the response is not JSON (or the JSON is not a dictionary). So, when parsing the JSON, use optional binding to gracefully handle nil or non-dictionary errors, perhaps examining the body of response if it fails, e.g.:
let task = session.downloadTaskWithURL(url!) { location, response, error in
if error == nil {
let data = NSData(contentsOfURL: location)
var error: NSError?
if let json = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: &error) as? NSDictionary {
println("json = \(json)")
} else {
println("error = \(error)")
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
println("not json; responseString = \(responseString)")
println(response)
}
}
}
task.resume()
Also, note, when using JSONObjectWithData, you not only want to gracefully check for an error, but you generally want to use the error parameter, too, as noted above.
BTW, make sure you include the latitude and longitude in the URL as described in the forecast.io API documentation, or else you'll get a non-JSON error response. Even if you fix the URL to avoid this error, you should still implement some graceful handling of errors, like above, or else your app might crash whenever there are any server issues.

Resources