How to check Optionals in Swift for nil values?
I'm trying to evaluate if parsing JSON was successful. This should print a error message when invalid json is passed, but fails with EXC_BAD_INSTRUCTION.
func parseJson(data: NSData) {
var error: NSError?
var json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
if let err = error {
println("error parsing json")
return
}
println("json parsed successfully")
}
Try running with valid json (works fine):
parseJson(NSData(data: "{}".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)))
Try with invalid json:
parseJson(NSData(data: "123".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)))
Did I miss the point on working with Optionals?
The crash is happening because JSONObjectWithData returns nil if there's an error. You then try to cast nil to NSDictionary. This works:
func parseJson(data: NSData) {
var error: NSError?
var json : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error)
if let err = error {
println("error parsing json")
return
}
println("json parsed successfully")
var jsonDict = json as NSDictionary
}
If you prefer, you can switch the type of var json to NSDictionary? so it directly handles nil. To do this you just need to add a '?' to the "as" keyword as so:
func parseJson(data: NSData) {
var error: NSError?
var json: NSDictionary? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary
if let err = error {
println("error parsing json")
return
}
// unwrap the optional value
if let jsonDictionary = json {
println("json parsed successfully")
// use the dictionary
}
}
I restructured your code a bit:
func parseJson(data: NSData) {
var error: NSError?
var json: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error)
if let jsonDict: NSDictionary = json as? NSDictionary{
println("json parsed successfully")
//do something with the nsdictionary...
}
else{
println("error parsing json")
}
}
parseJson(NSData(data: "{}".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)))
parseJson(NSData(data: "123".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)))
The problem wasn't with your if code, but making json an NSDictionary when it isn't one when the method fails. Also, this code now checks if an NSDictionary was returned instead of checking for the error itself.
NSJSONSerialization.JSONObjectWithData returns an optional value that has been implicitly unwrapped, which means that the return value could be nil. In the invalid JSON case, a nil optional type was returned. So, your var json must also be an optional type since it could be nil. Further more, if var json is optional type then you must downcast it as optional by adding ? next to as. This is the final result
var json: NSDictionary? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary
I was getting fatal error: unexpectedly found nil while unwrapping an Optional value until I made the NSDictionary type casting optional, e.g. as? NSDictionary instead of as Dictionary
Related
I'm having this issue where I can't access values from JSON response,
the response is : {"result":[true]}
and when the JSON gets it with this code
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
let result:String = json["result"]
print(result)
}catch {
print("Error with Json: \(error)")
}
I get an error, and the when I did the debug, I saw that json had the following
how json is stored
is there anyway to access the result from json ? it didn't work treating it as an array nor as dictionary
any ideas ?
thanks
result is not String, it's an Array of Bool (represented by the brackets).
Basically do not annotate types unless the compiler needs them.
Cast the JSON to the proper type and use Swift native collection types. It's also recommended to use optional bindings to avoid unexpected crashes.
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? [String:AnyObject],
result = json["result"] as? [Bool] where !result.isEmpty {
print(result[0])
}
} catch {
print("Error with Json: \(error)")
}
Try like this
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as! NSDictionary
let result = json["result"] as! NSArray
print(result)
let boole = result[0];
}catch {
print("Error with Json: \(error)")
}
I have a php file which create a JSON array. This the JSON output.
[{"employee_id":"1","employee_name":"Steve","employee_designation":"VP","employee_salary":"60000"},{"employee_id":"2","employee_name":"Robert","employee_designation":"Executive","employee_salary":"20000"},{"employee_id":"3","employee_name":"Luci","employee_designation":"Manager","employee_salary":"40000"},{"employee_id":"4","employee_name":"Joe","employee_designation":"Executive","employee_salary":"25000"},{"employee_id":"5","employee_name":"Julia","employee_designation":"Trainee","employee_salary":"10000"}]
I want to parse this array using swift in my app. So I used the following code to parse the JSON array
func jsonParser() {
let urlPath = "xxxxxxxxx/dbretrieve.php"
guard let endpoint = NSURL(string: urlPath) else { print("Error creating endpoint");return }
let request = NSMutableURLRequest(URL:endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
guard let dat = data else { throw JSONError.NoData }
guard let json = try NSJSONSerialization.JSONObjectWithData(dat, options:.AllowFragments) as? NSArray else { throw JSONError.ConversionFailed }
print(json)
} catch let error as JSONError {
print(error.rawValue)
} catch {
print(error)
}
}.resume()
}
but I get the following error
Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}
What's the mistake I made?
I checked your json array. Its perfect.
The point where the issue might be serialization.
Depends on data you get in response
Try to disable debugging mode in PHP
Try below serialisation code
Sample Code:
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
print(json)
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
I am getting {"result":"0"} string . The problems is that my serialization function cant serialize it
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error: &err) as NSDictionary
if(err != nil) {
println(err!.localizedDescription)
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Error could not parse JSON: '\(jsonStr)'")
}
I am getting answer on console:
Body: Optional({"result":"0"})
Error could not parse JSON: Optional({"result":"0"})
I the function cant serialize it?
This is because your variable 'data' is optional type. You have to either unwrape it. Or use ! sign. But the later is risky as it might have nil also and app will crash. So do this
if let dat = data
{
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(dat, options: .MutableLeaves, error: &err) as NSDictionary
}
I'm trying to learn Swift and am struggling with understanding a piece about handling JSON and get "fatal error: unexpectedly found nil while unwrapping an Optional value" from using this code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlPath = "http://api.worldweatheronline.com/free/v2/marine.ashx?key=45e8e583134b4371a2fa57a9e5776&format=xml&q=45,-2"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if ((error) != nil) {
println(error)
} else {
var err: NSError?
println("URL: \(url)")
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
println(jsonResult)
}
})
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The API key I'm using is free so don't worry about using it. The JSON results should look something like this but I am instead greeted with:
Task completed
URL: http://api.worldweatheronline.com/free/v2/marine.ashx?key=45e8e583134b4371a2fa57a9e5776&format=xml&q=45,-2
fatal error: unexpectedly found nil while unwrapping an Optional value
I'm assuming the jsonResult variable is what's causing the problem, but even if I try to force unwrap it (I think that's what I'm doing?) with something like
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
it still fails with "expected type after 'as'."
Everything is fail-able here, a website can even return 200 but return nothing, the data can come corrupted, and, in your case, I tried calling the URL you are using and found out, it's actually XML, so... there's your problem?
A more robust approach (assuming that you still want to parse JSON, maybe from another site? The ISS has an open API) is to use the if let to unwrap optionals and then on the else case deal with the errors, rather than ! your way to success.
Here's a very extensive check of everything
task = session.dataTaskWithRequest(NSURLRequest(URL: url), completionHandler: { [unowned self] (data, response :NSURLResponse!, errorRequest) -> Void in
// Check the HTTP Header
if let responseHTTP = response as? NSHTTPURLResponse
{
if responseHTTP.statusCode != 200
{
println("Received status code \(responseHTTP.statusCode)")
return
}
}
else
{
println("Non-HTTP Response received")
return
}
var errorParsing : NSError?
if let JSON = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &errorParsing) as? NSDictionary
{
// Do the parsing
}
else
{
println("Error parsing: \(errorParsing)")
}
})
task!.resume()
NSJSONSerialization.JSONObjectWithData... returns an optional NSData?. This means that you can't cast it directly to NSDictionary. To fix the crash you will need to use ?as which means your jsonResult will either be a NSDictionary or nil
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) ?as NSDictionary
In your case, you can safely unwrap the optional by doing this.
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let jsonResult = jsonResult ?as NSDictionary {
println(jsonResult)
}
Why am I unable to parse JSON from the HTTP response via the following code?
if let url = NSURL(string: "https://2ch.hk/b/threads.json") {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) {
(data, response, error) in
var jsonError: NSError?
let jsonDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as [String: AnyObject]
if jsonError != nil {
return
}
// ...
}
task.resume()
}
Output
fatal error: unexpectedly found nil while unwrapping an Optional value
What am I doing wrong? How can I fix it?
Thanks in advance.
This is a bit late.... but I think you are trying to parse the error as well so add an else part and and the dictionary to be serialized will only be parsed if there is the data ... your code can be modified as follows
if let url = NSURL(string: "https://2ch.hk/b/threads.json") {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) {
(data, response, error) in
if (jsonError != nil) {
return
} else {
var jsonError: NSError?
let jsonDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as [String: AnyObject]}
// ...
}
task.resume()
}