I'm brand new to Swift development. I'm trying to build a basic app which will read a feed from Google Calendar which is returned in JSON.
I am able to use a NSURLConnection.sendAsynchronousRequest call as described in this thread: Make REST API call in Swift and I get a result of some sorts.
My problem is I am unable to parse the JSON into some meaningful objects from which I can create a list of upcoming events.
My code this far is:
var url : String = "some url"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil){
// No idea how to parse this object as it seems to be a NSDictionary but battling to interact with it.
}
else
{
}
})
Any help would be greatly appreciated.
If you put a breakpoint after
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
then you'll be able to see the dictionary key/value pairs. I copied/pasted your code and see this:
You can access the values by using jsonResult.objectForKey("keyName"). I'm also going to downcast using as DataType.
Example)
let encoding = jsonResult.objectForKey("encoding") as String
let version = jsonResult.objectForKey("version") as String
let feed = jsonResult.objectForKey("feed") as NSDictionary
Related
I was reading the different ways to parse REST API calls in Swift and came across the following:
var url : String = "http://google.com?test=toto&test2=titi"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil) {
// process jsonResult
} else {
// couldn't load JSON, look at error
}
})
The one line that makes no sense to me is var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil. We already captured our NSError parameter and stored it in a variable called error, and now we're overwriting that and making it nil in our first line in the closure? Or if somehow Swift then performs a downcast of the error from type NSError! to AutoreleasingUnsafeMutablePointer<NSError?>, then can someone explain how that happens?
Thanks!
AutoreleasingUnsafeMutablePointer is the equivalent to NSError** in Objective-C, which is used in methods as an inout expression. The syntax looks very strange.
The most reliable way is to consider both errors and define a second error variable. As GET is the default HTTP method of NSURLRequest, an immutable request is sufficient.
let url = "http://google.com?test=toto&test2=titi"
let request = NSURLRequest(URL: NSURL(string: url)!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if error != nil {
// handle NSURLConnection error
} else {
var jsonError : NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: &jsonError) as? NSDictionary {
// process jsonResult
} else {
// couldn't load JSON, look at jsonError
}
}
})
I'm trying to get data from an NSDictionary in Swift but I'm not able to do this.
Here is the code
let urlPath = "http://ws-report.goocity.it/Stats/json/GetReport"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
var result: NSDictionary
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)")
}
else{
//var arr : NSArray = jsonResult.valueForKey("Stats") as NSArray
//var cash: String? = jsonResult.valueForKey("Cash") as? NSString
let stats = jsonResult["Stats"]! as [[String : AnyObject]]
}
})
task.resume()
You can see the JSON clicking on the urlPath.
I still receive the following error on the let stats = ...
Thread 5: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
I've just checked out few other posts here on stackoverflow and anywhere, but no answer is good for my issue.
I need to get all JSON field values in many variables (like cash, lastUpdate, totalPartners, etc).
Your json is not an array of Dictionaries, it is just a Dictionary.
Change [[String: AnyObject]] to [String: AnyObject] and it will work.
No matter what I do it seems I'm unsuccessful in sending requests. Given the below sample code I copied word for word just to see the results. Yet nothing happens, I'm really confused and need help figuring out why i can send requests fine with objective c but no matter how many variations NSURLRequest NSURLSession I try it never works on swift.
var url : String = "http://google.com?test=toto&test2=titi"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(),
completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil) {
println("help me")
// process jsonResult
} else {
println("hmmm")
// couldn't load JSON, look at error
}
})
DO NOT test network asynchronous requests on a commande line project.
The execution flow will stop before the asynchronousRequest terminates... You would need to add a run loop for that. Check out this link for an example.
You should take the habit to print out everything you get from a request, to understand what is going on. You can comment out everything after you are sure the request is working as expected.
var url : String = "http://google.com?test=toto&test2=titi"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(),
completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
println("OK")
var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)\n\n")
println("Response: \(response)")
var err:NSError?
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary
if (jsonResult != nil) {
println("jsonresult : \(jsonResult)")
// process jsonResult
} else {
println(err.debugDescription)
// couldn't load JSON, look at error
}
})
I added a line to print the NSData converted to a NSString.
Here the data is nil.
That explains the JSON parsing error.
Also, the way you create the error is not right. Check out my version to correct it.
You aren't checking for the results of your various variables. If you're trying to diagnose problems, you have to look at each critical variable. For example, first check to see if the request succeeded and if not, quit immediately. Otherwise, try parsing the JSON, showing the resulting object if successful, but showing the parsing error on failure. If the JSON parsing fails (as it will with this URL), you might even look at the string representation of the returned data.
FYI, the handling of the NSError object with NSJSONSerialization is also incorrect. It should look like:
var parsingError: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parsingError) as? NSDictionary {
// success, use `jsonResult`
} else {
// failure, look at `parsingError`
}
Putting that all together:
let url = "http://google.com?test=toto&test2=titi"
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) {
response, data, error in
if data == nil {
println("request error: \(error)")
return
}
var parsingError: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parsingError) as? NSDictionary {
println("json parsed: \(jsonResult)")
} else {
println("parsing error: \(parsingError)")
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("data: \(responseString)")
}
}
This will, with this particular URL, fail, because the response is not JSON, but this will also show you the string representation of the response.
I am working on an app that displays the live Bitcoin price. I am using 2 APIs to do this - one plaintext, and one JSON. I am having a bit of trouble with the JSON API.
Here's a bit of my Swift code
func BTCFallback(){
var string2 = currencySelector.currentTitle
var url = NSURL(string:"https://bitpay.com/api/rates/" + (string2)!)
var request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:fallback)
var data = NSData(contentsOfURL:url);
let value = NSString(string: USD.text).doubleValue / NSString(data:data, encoding:NSUTF8StringEncoding).doubleValue
// Define JSON string
var JSONString = "\(data)"
// Get NSData using string
if let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
// Parse JSONData into JSON object
var parsingError: NSError?
if let JSONObject = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &parsingError) as? [String: AnyObject] {
// If the parsing was successful grab the rate object
var rateObject: AnyObject? = JSONObject["rate"]
// Make sure the rate object is the expected type
if let rate = rateObject as? Float {
println("rate is \(rate)")
BTC.text = "\(rate)"
}
} else {
// There was an error parsing the JSON data
println("Error parsing JSON: \(parsingError)")
BTC.text = "err1"
}
}
}
In the above code, currencySelector.currentTitle is equal to an ISO currency code, for instance USD. BTC.text is a UI element.
The expected behavior is that the code will set the counterpart of "rate" as the text of BTC.text. In case this helps, the API returns something like {"code":"USD","name":"US Dollar","rate":376.71}. I would want, using the above example, to have BTC.text set to 376.71
Here's what's happening: the consoe gives the error
Error parsing JSON: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (JSON text did not start with array or object and option to allow fragments not set.) UserInfo=0x16eb0f60 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.})
What am I doing wrong? Thanks in advance!
It is all a matter of handling the returned data and de-serialization.
Here is example code, note that the handling of Optionals should be better, this is just to demonstrate the basic code. For example purposes I used a simple synchronous web call.:
var url: NSURL! = NSURL(string:"https://bitpay.com/api/rates/AUD")
var request = NSURLRequest(URL: url)
var response: NSURLResponse?
var error: NSError?
var data: NSData? = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&error)
println("data: \(data)")
if let data: NSData = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&error) {
println("data: \(data)")
var parsingError: NSError?
if let rateDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parsingError) as NSDictionary? {
println("rateDictionary: \(rateDictionary)")
// If the parsing was successful grab the rate object
if var rateString: AnyObject = rateDictionary["rate"] {
println("rateString: \(rateString)")
// Make sure the rate object is the expected type
if let rate = rateString.floatValue {
println("rate is \(rate)")
}
}
}
}
Ouput:
data: Optional(7b22636f 6465223a 22415544 222c226e 616d6522 3a224175 73747261 6c69616e 20446f6c 6c617222 2c227261 7465223a 3430372e 39393137 7d)
rateDictionary: {
code = AUD;
name = "Australian Dollar";
rate = "407.9917";
}
rateString: 407.9917
rate is 407.992
I'm trying to fetch movie information in JSON format using JSONSerialization in Swift and save it as an NSDictionary. However, calling the Rotten Tomatoes API (in which information is nested) will cause my Playground to crash (without giving me any useful errors).
I know the code is somewhat valid, since calling other APIs which don't nest their data works (but don't fulfill my need).
Here's the code:
func getJSON(urlToRequest: String) -> NSDictionary {
var url = NSURL(string: urlToRequest)
var error: NSError?
let jsonData: NSData = NSData.dataWithContentsOfURL(url , options: NSDataReadingOptions.DataReadingMapped, error: nil)
let jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
return jsonDict
}
let movieData: NSDictionary = getJSON(apiURL)
here's a sample from the Rotten Tomatoes API JSON (shortened for illustration purposes, this is the actual JSON file, not the output from my code)
{
"id": 770672122,
"title": "Toy Story 3",
"year": 2010,
"genres": ["Animation", "Kids & Family", "Science Fiction & Fantasy", "Comedy"],
"release_dates": {
"theater": "2010-06-18",
"dvd": "2010-11-02"
},
"ratings": {
"critics_rating": "Certified Fresh",
"critics_score": 99,
"audience_rating": "Upright",
"audience_score": 89
}
}
This code works for me in a Swift iOS app (crashes Xcode when run in a playground:
func getJSON(urlToRequest: String) -> NSDictionary {
var url = NSURL(string: urlToRequest)
var error: NSError?
let jsonData: NSData = NSData.dataWithContentsOfURL(url , options: NSDataReadingOptions.DataReadingMapped, error: nil)
let jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
return jsonDict
}
let apiURL = "http://api.rottentomatoes.com/api/public/v1.0/movies/770672122.json?apikey=9p4xwzpgt8xp2vahnyx2sc6g"
let movieData: NSDictionary = getJSON(apiURL)
println(movieData)
It looks like the crash you are getting is a playground error.
Note, Swift is basically guaranteed to have bugs, it is an early Beta.
Also explicitly state they the JSON is example code, not fro running your code.