SwiftyJSON Problems Swift 2 - json

I have a API URL that return JSON data, which you can find here https://itunes.apple.com/us/rss/topmovies/limit=25/json
I want to return all of the movie titles in this JSON data.
I am using the SwiftyJSON Framework to parse the JSON data from the URL. I am using a NSURLSession.dataTask to begin the Parse from the URL.
The problem is that the JSON data I want to return is not returning anything.
Here is some code -
JSON Data URL -
let url = "https://itunes.apple.com/us/rss/topmovies/limit=25/json"
Retrieving JSON Data
func getTheJSONData() throws {
let theURL = NSURL(string: url)
let request = NSURLRequest(URL: theURL!)
//let JSONError : NSError?
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
if error == nil{
let swiftyJSON = JSON(data: data!, options: NSJSONReadingOptions.MutableContainers, error: nil)
let Name = swiftyJSON["feed"]["entry"][0]["im:name"]["label"].stringValue
//prints nil..
print(Name)
}
} catch {
// report error
print(error)
}
}
task.resume()
}

Your code does exactly what it's supposed to: it prints the title of a movie.
With this:
let Name = swiftyJSON["feed"]["entry"][0]["im:name"]["label"].stringValue
print(Name)
you get the title of one movie because you're getting the first element of the array with [0].
Since you want to get all movies titles, instead of working with the first element, work with all elements. Example:
let swiftyJSON = JSON(data: data!, options: NSJSONReadingOptions.MutableContainers, error: nil)
let movies = swiftyJSON["feed"]["entry"].arrayValue
let titles = movies.map { $0["im:name"]["label"].stringValue }
print(titles)
print(titles) gives an array of titles:
["Jurassic World", "Avengers: Age of Ultron", "Spy", "Entourage", "Pixels", "Froning", "Pitch Perfect 2", "Cartel Land", "Aladdin", "Furious 7 (Extended Edition)", "Magic Mike XXL", "\'71", "The Age of Adaline", "Cast Away", "Cinderella (2015)", "San Andreas", "Mad Max: Fury Road", "Hotel Transylvania", "Paddington", "Mission: Impossible - Ghost Protocol", "Aloha", "Dope", "Man on Wire", "Me and Earl and the Dying Girl", "The Overnight"]
By the way, as the compiler says with the warning, there's no need to use do catch for JSON() because it doesn't throw.

Related

Access variable do-catch statement on Swift [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
I am developing an application that json parse. I'm using the AlertView for json messages. But I can not access the jsonmessage variable in the AlertView. if I put the AlertView in DO I get this error: "libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)"
Sorry for my bad English. This is my code:
request.httpBody = postParameters.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = myJSON {
var jsonmessage : String!
jsonmessage = parseJSON["message"] as! String?
print(jsonmessage)
}
} catch {
}
}
task.resume()
let alert = UIAlertController(title: "Alert", message: jsonmessage /*not accessible*/ , preferredStyle: .alert)
alert.addAction(UIAlertAction(title:"Ok", style:UIAlertActionStyle.default, handler:{ (UIAlertAction) in
_ = self.navigationController?.popToRootViewController(animated: true)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
}))
self.present(alert, animated: true, completion: nil)
As you have discovered jsonMessage is not accessible from where you are trying to access it.
This is because of a few reasons:
The request is an asynchronous task that runs in the background and takes some time to complete. So the alert view code actually runs before the jsonMessage is returned
The variable jsonMessage is also out of scope where you are trying to call it.
To help explain:
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
let fakeMessage = "hi"
// data, response, error and fakeMessage only exist here upto the closing bracket.
}
task.resume()
// fakeMessage doesn't exist here at all.
To resolve your issue you can either present your alert from within the closure (where I have put fakeMessage) or you se a completionHandler to return jsonMessage when it is ready and then show the alert.
Method 1
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = myJSON {
var jsonmessage : String!
jsonmessage = parseJSON["message"] as! String?
DispatchQueue.main.async {
// some helper function to show a basic alert
self.presentAlert(title: "Response", message: jsonMessage)
}
}
} catch {
}
}
task.resume()
Method 2
func fetchSomething(completion: #escaping (String -> Void)?) {
// setup request
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
let fakeMessage = "hi"
completion(fakeMessage)
}
task.resume()
}
then you can use it this way
self.fetchSomething { response in
DispatchQueue.main.async {
// some helper function to show a basic alert
self.presentAlert(title: "Response", message: jsonMessage)
}
}
First of all, you are doing a request that's asynchronous. The jsonmessage variable is set once you get the response. But you create the UIAlertController before this happens. I'm guessing you wish to display the alert once you get a response?
Also, you can't access the jsonmessage variable outside of its scope. To fix this, move var jsonmessage : String! so that it belongs to the same scope as the UIAlertController.
You should be able to move your alert into the do catch statement, but you have to make sure that the alert is displayed on the main thread.

Error parsing JSON in swift and loop in array

I have an api which return a JSON and i want to parse this JSON and use it in my application.
I have tried the get method from this: swift JSON login REST with post and get response example
Code:
func makeGetCall() {
// Set up the URL request
let todoEndpoint: String = "my link"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /public/api/services")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo
// let's just print it to prove we can access it
print("The todo is: " + todo.description)
// the todo object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
guard let todoTitle = todo["name"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
And i got as an output: error trying to convert data to JSON..
My JSON IS:
[
{
"id": 1,
"name": "Services 1",
"description": "This is a description of Services 1. This is a description of Services 1 This is a description of Services 1. ",
"created_at": null,
"updated_at": null
},
{
"id": 2,
"name": "Services 2",
"description": "This is a description of Services 2. This is a description of Services 2 This is a description of Services 2. ",
"created_at": null,
"updated_at": null
}
]
Why i got error parsing the JSON?
Also, how to loop for the array and print each item?
For example:
service 1 description is: This is a description of Services 1. This is
a description of Services 1 This is a description of Services 1.
service 2 description is: This is a description of Services 2. This is
a description of Services 2 This is a description of Services 2.
Please read the JSON carefully. The root object is clearly an array ([])
guard let todos = try JSONSerialization.jsonObject(with: responseData) as? [[String: Any]] else {
print("error trying to convert data to JSON")
return
}
for todo in todos {
print(todo["name"] as? String ?? "n/a")
}
However I recommend to use the Decodable protocol. Declare this struct outside the class
struct Service : Decodable {
let id : Int
let name, description : String
let createdAt : String?
let updatedAt : String?
}
and decode the JSON this way
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let todos = try decoder.decode([Service].self, from: responseData)
for todo in todos {
print(todo.name)
}
} catch { print(error) }
Side note:
The line guard let responseData = data else { will never reach the else clause. If error is nil – which has been checked already – then it's guaranteed that data has a value.
I think you're making small mistake, you have a list of todo, parsing won't give you the todo itself. It will give you the Array of todo
In Swift4:
//assume that you have the JSON String as Data
guard let data = data else {
return
}
let json = try? JSONSerialization.jsonObject(with: response.data!, options: [])
if let array = json as? [[String: Any]] {
for todo in array {
// parse todo component
if let name = todo["name"] as? String {
print("Name : \(name)")
}
// other properties parse in the same way
}
}

JSONSerialization with URLSession.shared.dataTask errors

As a part of teaching myself Swift, I am working on a Weather App. I am currently attempting to integrate weather alerts. I use a struct called AlertData to initialize data returned from the API call to weather.gov after serializing the returned data from an API call. Or, at least that is the plan. I have modeled my classes off of other classes that request data from weather.gov, but to get an alert, I need to be able to send variable parameters in my dataTask. I use the URL extension from Apple's App Development with Swift (code below) and have the code set to issue the parameters with the users current location to get alerts where the user is currently.
My problem comes when I attempt to construct the API call to weather.gov in my AlertDataController class(code below). Xcode keeps throwing different errors and I am not sure why. I would like to use a guard statement as I have in my code below, but that throws an error of "Cannot force unwrap value of non-optional type '[[String : Any]]'" in my code where shown. It also throws the same error when I make it a simple constant assignment after unwrapping as the extension returns an optional URL.
The same code works flawlessly when I construct the URL from a string in the guard statement directly as in:
guard let url = URL(string: (baseURL + locationString + stations)) else {
What am I missing? Where my error is thrown is inside the dataTask, and regardless of how it got there, the variable url is an unwrapped URL. Thanks in advance.
Controller class:
import Foundation
import CoreLocation
struct AlertDataController {
func checkWxAlert(location: CLLocation, completion: #escaping (AlertData?) -> Void) {
let baseURL = URL(string: "https://api.weather.gov/alert")!
let locationString = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
var query = [
"active": "1",
"point": locationString
]
guard let url = baseURL.withQueries(query) else {
completion(nil)
print("Unable to build URL in AlertDataController.checkWxAlert with supplied queries.")
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data,
let rawJSON = try? JSONSerialization.jsonObject(with: data),
let json = rawJSON as? [String: Any],
let featuresDict = json["features"] as? [[String: Any]],
let propertiesArray = featuresDict!["properties"] as? [String: Any] {
Error: Cannot force unwrap value of non-optional type '[[String : Any]]'
let alertData = AlertData(json: propertiesArray)
completion(alertData)
} else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
}
task.resume()
}
}
URL extension:
import Foundation
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
func withHTTPS() -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.scheme = "https"
return components?.url
}
}
If featuresDict is really an array, you cannot use featuresDict["properties"] syntax. That subscript with string syntax is only for dictionaries. But you've apparently got an array of dictionaries.
You could iterate through the featuresDict array (which I'll rename to featuresArray to avoid confusion), you could do that after you finish unwrapping it. Or, if just want an array of the values associated with the properties key for each of those dictionaries, then flatMap is probably a good choice.
For example:
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
error == nil,
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
let featuresArray = json["features"] as? [[String: Any]] else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
let propertiesArray = featuresArray.flatMap { $0["properties"] }
let alertData = AlertData(json: propertiesArray)
completion(alertData)
}
Or, if AlertData is expecting each of those properties to be, themselves, a dictionary, you might do:
let propertiesArray = featuresArray.flatMap { $0["properties"] as? [String: Any] }
Just replace that cast with whatever type your AlertData is expecting in its array, json.
Or, if you're only interested in the first property, you'd use first rather than flatMap.
The error
Cannot force unwrap value of non-optional type '[[String : Any]]'
is very clear. It occurs because in the optional binding expression featuresDict is already unwrapped when the next condition is evaluated.
Just remove the exclamation mark
... let propertiesArray = featuresDict["properties"] as? [String: Any] {
The error is not related at all to the way the URL is created.

Unwrapping JSON from Itunes API - IOS App

Having an issue with my program. I would appreciate it if someone could help out. I have tried for weeks to parse the JSON files fetched from the iTunes API
(itunes.apple.com/search?term=song+you+want+to+search&entity=songTrack).
However, my answers are never displayed on my tableview and an error always shows up in the terminal:
"2017-11-14 17:25:28.809190+0100 Itunes Learning[32409:6240818] [MC] Lazy loading NSBundle MobileCoreServices.framework
2017-11-14 17:25:28.810264+0100 Itunes Learning[32409:6240818] [MC] Loaded MobileCoreServices.framework
2017-11-14 17:25:28.823734+0100 Itunes Learning[32409:6240818] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/cyprianzander/Library/Developer/CoreSimulator/Devices/D52FD9D5-B6E4-4CE0-99E4-6E0EE15A680D/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
2017-11-14 17:25:29.875534+0100 Itunes Learning[32409:6240900] Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
(lldb) "
This is approximately how the JSON file is set up:
{“resultCount” : 50, “results”: [ {“trackName”:”name”, ”artistName”:”name2”}, {“trackName”:”name3”, “artistName”:”name4”} ] }
(An array of objects inside an array - meaning the first object is on the far outside).
I have tried my function with another API, which did work. I have the feeling that the main reason as to why this happens, is because the iTunes API JSON file is very complex. It is an assortment of very long objects inside an array, which is inside a smaller list of objects. However, the other one was only and array of objects.
Here is my code: (I have noticed that the problem occurs while parsing the data I need. The only thing I need to know is how to properly unwrap my JSON file)
func parseData(searchTerm: String) {
fetchedSong = []
let itunesSearchTerm = searchTerm.replacingOccurrences(of: " ", with: "+", options: .caseInsensitive, range: nil)
let escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
let urlString = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&entity=song"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
// If there is an error in the web request, print it to the console
print(error)
return
}
else {
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
print(fetchedData)
for eachFetchedSong in fetchedData {
let eachSong = eachFetchedSong as! [String: Any]
let song = eachSong["trackName"] as! String
let artist = eachSong["artistName"] as! String
self.fetchedSong.append(songs(song: song, artist : artist))
}
self.SongTableView.reloadData()
}
catch {
print("An error occured while decoding the JSON object")
}
}
}.resume()
}
If anyone could help me, I would be extremely happy, especially because I have been stuck with this for three weeks, continuously trying different techniques (this one seemed the most successful).
Your JSON data is not an array. It is a dictionary with two key/value pairs. The first is the key "resultCount" with a value of 50, and the second is the key "results" with an array as its value.
Never use as! when parsing JSON, since this will crash your app if you get an unexpected result. Don't use .mutableLeaves unless you can explain to us what it does and why you need it. Don't use NSArray in your Swift code.
Handling one error and crashing on others is pointless. I'd write
if let fetchedDict = try? JSONSerialization(...) as? [String:Any],
let fetchedArray = fetchedDict ["results"] as? [[String:Any]] {
for dict in fetchedArray {
if let song = dict ["trackName"] as? String,
let artist = dict ["artistName"] as? String {
...
}
}
}

How to parse this specific JSON data in Swift 2.0

I'm trying to parse Json Data from an API :
{
"title": "Mr. Robot",
"first_aired": "2015-06-24",
"network": "USA Network",
"channels": [
{
"id": 12,
"name": "USA",
"short_name": "usa",
"channel_type": "television"
}
],
The Code I'm use is:
var TVArray : [TVInfo] = []
var task : NSURLSessionTask?
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateJSON(data)
}
else {
}
}
}
task!.resume()
}
func updateJSON (data: NSData!) {
let JSONData = (try! NSJSONSerialization.JSONObjectWithData(data, options: []))
TVArray.removeAll(keepCapacity: true)
if let jsonArray = JSONData {
for j in jsonArray {
let title = jsonResult["title"] as! String
let firstAired = jsonResult["first_aired"] as! String
let network = jsonResult["network"] as! String
let channelName = JsonResult["channels"][0]["name"] as! String
let TV = TVInfo(title: title, firstAired: firstAired, network: network, channelName: channelName)
TVArray.append(TV)
}
}
collectionview.reloadData()
}
}
When I use the above code I get an error 'Initializer for conditional binding must have Optional type, not 'AnyObject'' in front of the line 'if let jsonArray = JsonData'. I've tried some methods I've seen on StackOverflow like the method in the link :
[Parsing JSON in swift 2.0
but it didn't work for me. I'm still a bit new to Swift, I really don't want to use SwiftyJSON. Is this the best way to parse this JSON data or is there a better way of doing it?
Since you've used NSJSONSerialization with try! (note the !, meaning it was forced) the value of JSONData is not an optional: you don't have to unwrap it with if let jsonArray = JSONData.
If you still want an optional value, use try? instead.
Otherwise you could also use try inside a do catch block to handle possible errors.
The type of JSONData is unknown, it needs to be known to be an Array for the following for loop.
use:
let JSONData = try! NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! NSArray
You do not need:
if let jsonArray = JSONData {
because you have already crashed if JSONData is nil from the preceding try!
You are better with:
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! NSArray
for j in jsonArray {
// ...
}
} catch {
// handle error
}
Because you have no control over the JSON you receive and crashing because of a server change is not a good idea.
Also really put some time into naming variables, JSONData is not data, it is an array obtained by parsing a JSON string.