JSON Parsing Swift, Array Has No Value Outside Nsurlsession

JSON parsing swift, array has no value outside NSURLSession

The idea is to use a "callback".

Here, I've made one for the NSArray you want to get:

completion: (dataArray: NSArray)->()

We create a function to get the array, and we add this callback to the function's signature:

func getDataArray(urlString: String, completion: (dataArray: NSArray)->())

and as soon as the array is ready we'll use the callback:

completion(dataArray: theNSArray)

Here's how the complete function could look like:

func getDataArray(urlString: String, completion: (dataArray: NSArray)->()) {
if let url = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(url) {(data, response, error) in
if error == nil {
if let data = data,
json1 = NSString(data: data, encoding: NSUTF8StringEncoding),
data1 = json1.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data1, options: [])
if let jsonArray = json as? NSArray {
completion(dataArray: jsonArray)
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("Error: no data")
}
} else {
print(error!.localizedDescription)
}
}.resume()
}
}

Now we use this function like this, no more asynchronous issues:

getDataArray("http://192.1.2.3/PhpProject1/getFullJson.php") { (dataArray) in
for dataDictionary in dataArray {
if let notId = dataDictionary["ID"] as? String {
self.IdDEc.append(notId)
}
}
print("id out count = ", self.IdDEc.count)
}

Swift 3 update + fixes and improvements.

func getContent(from url: String, completion: @escaping ([[String: Any]])->()) {
if let url = URL(string: url) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil {
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let content = json as? [[String: Any]] { // array of dictionaries
completion(content)
}
} catch {
// error while decoding JSON
print(error.localizedDescription)
}
} else {
print("Error: no data")
}
} else {
// network-related error
print(error!.localizedDescription)
}
}.resume()
}
}

getContent(from: "http://192.1.2.3/PhpProject1/getFullJson.php") { (result) in
// 'result' is what was given to 'completion', an array of dictionaries
for dataDictionary in result {
if let notId = dataDictionary["ID"] as? String {
// ...
}
}
}

difficulty in returning an array of class after being fetched from the server


print("\(movie)")

This is returning nil because that line get executed before you have received any data from server. so movie will not be containing any data at that time. and once you get response from server, you store data in movie and then print it.

If you are facing issue returning data from this function you can use the code given in the below link.
Here

Array has no data when async task is complete

You're confusing how Async works. This is how I would do it:

// The completionBlock is called with the result of your JSON file loading
func fillFromFile(completionBlock: @escaping ([Asset_Content]) -> ()) {
let url = "URLSTRING"

LoadJSONFile(from: url) { (result) in
// The code inside this block would be called when LoadJSONFile is completed. this could happen very quickly, or could take a long time

//.map is an easier way to transform/iterate over an array
let newContentArray = json.map {

let category = json["BIGCATEGORY"] as? String
let diagnosis = json["DIAGNOSIS"] as? String
let perspective = json["PERSPECTIVE"] as? String
let name = json["NAME"] as? String
let title = json["Title"] as? String
let UnparsedTags = json["TAGS"] as? String
let filename = json["FILENAME"] as? String

let tagArray = UnparsedTags?.characters.split(separator: ",")
for tag in tagArray!{
if(!self.ListOfTags.contains(String(tag))){
self.ListOfTags.append(String(tag))
}
}

let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
// This is a return to the map closure. We are still in the LoadJSONFile completion block
return asset
}
print("return count ", newContentArray.count)
// This is the point at which the passed completion block is called.
completionBlock(newContentArray)
}
}

Hopefully this explains what you need to do in your situation. Remember that the whole point of an async task is you don't know how long it will take, and waiting for it to finish does not stop the execution of the current block of code.

Swift3 Json Parsing - how to access relevant fields

When you deal with JSON I think it is helpful to use tools like:
jsonformatter.curiousconcept.com
or
jsonlint.com
which help me a lot understanding the structure and what kind of data I am dealing with.

If you look at your json in you can notice that, as Eric Aya said, data is an array not a dictionary:

{
"currentPage": 1,
"numberOfPages": 1,
"totalResults": 1,
"data": [{
"id": "1P45iR",
"name": "Budweiser",
"nameDisplay": "Budweiser",
"description": "Known as \u201cThe King of Beers\u201d, Budweiser was first introduced by Adolphus Busch in 1876 and is brewed with the same high quality standards today. Budweiser is a medium-bodied, flavorful, crisp American-style lager, craft brewed with a blend of premium hop varieties, and associated with the core American values of celebration and optimism.",
"abv": "5",
"glasswareId": 5,
"srmId": 5,
"availableId": 1,
"styleId": 93,
"isOrganic": "N",
"labels": {
"icon": "https:\/\/s3.amazonaws.com\/brewerydbapi\/beer\/1P45iR\/upload_Y13vwL-icon.png",
"medium": "https:\/\/s3.amazonaws.com\/brewerydbapi\/beer\/1P45iR\/upload_Y13vwL-medium.png",
"large": "https:\/\/s3.amazonaws.com\/brewerydbapi\/beer\/1P45iR\/upload_Y13vwL-large.png"
},
"status": "verified",
"statusDisplay": "Verified",
"servingTemperature": "cold",
"servingTemperatureDisplay": "Cold - (4-7C\/39-45F)",
"createDate": "2012-01-03 02:42:55",
"updateDate": "2016-03-21 19:54:11",
"glass": {
"id": 5,
"name": "Pint",
"createDate": "2012-01-03 02:41:33"
},
"srm": {
"id": 5,
"name": "5",
"hex": "FBB123"
},
"available": {
"id": 1,
"name": "Year Round",
"description": "Available year round as a staple beer."
},
"style": {
"id": 93,
"categoryId": 8,
"category": {
"id": 8,
"name": "North American Lager",
"createDate": "2012-03-21 20:06:46"
},
"name": "American-Style Lager",
"shortName": "American Lager",
"description": "Light in body and very light to straw in color, American lagers are very clean and crisp and aggressively carbonated. Flavor components should b e subtle and complex, with no one ingredient dominating the others. Malt sweetness is light to mild. Corn, rice, or other grain or sugar adjuncts are often used. Hop bitterness, flavor and aroma are negligible to very light. Light fruity esters are acceptable. Chill haze and diacetyl should be absent.",
"ibuMin": "5",
"ibuMax": "13",
"abvMin": "3.8",
"abvMax": "5",
"srmMin": "2",
"srmMax": "4",
"ogMin": "1.04",
"fgMin": "1.006",
"fgMax": "1.01",
"createDate": "2012-03-21 20:06:46",
"updateDate": "2015-04-07 15:39:26"
}
}],
"status": "success"
}

The following code works but I am sure there is a better way to get abv with less code:

 override func viewDidLoad()
{
super.viewDidLoad()

let url = URL(string: "http://api.brewerydb.com/v2/beers?key=e3bdce7d0a80584c784cdc4b02459add&name=budweiser")

URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error!)

}
else {

do {

let parsedData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any]


let jsonArrayData = parsedData?["data"] as! NSArray
let data = jsonArrayData[0] as! NSDictionary
let abv = data["abv"]
print(abv)//Optional(5) in your case


} catch let error as NSError {
print(error)
}
}

}.resume()
}

Parsing fetched JSON to dictionary in Swift 3

Look into completion blocks and asynchronous functions. Your getMovieData is returning before the datatask's completion handler is called.

Your function, instead of returning, will call the completion block passed in and should change to:

private func getMovieData(movieTitle: String, completion: @escaping ([String:Any]) -> Void) {

// declare a dictionary for the parsed JSON to go in
var movieData = [String: Any]()

// prepare the url in proper type
let url = URL(string: "http://www.omdbapi.com/?t=\(movieTitle)")

// get the JSON from OMDB, parse it and store it into movieData
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
do {
movieData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
completion(movieData)
} catch let error as NSError {
print(error)
}
}).resume()
}

How do I get a value from an NSURLSession task into an instance variable?

The dataTaskWithURL method makes an async call, so as soon as you do task.resume() it will jump to the next line, and json["shows"] will return nil as the dictionary is empty at this point.

I would recommend moving that logic to a completion handler somewhere in your class. Something along the lines of:

func getShowsFromService() {
let myURL = NSURL(string: "https://myurl.com/srvc/shows.php")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(myURL!, completionHandler: handleResult)
task.resume()
}

//-handle your result
func handleResult(data: NSData?, response: NSURLResponse?, error: NSError?) {
guard let data = data else {
print("Error: \(error!.code)")
print("\(error!.localizedDescription)")
return
}

do {
if let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! NSDictionary {
if let shows = json["shows"] as! NSArray {
//- this is still in a separate thread
//- lets go back to the main thread!
dispatch_async(dispatch_get_main_queue(), {
//- this happens in the main thread
for show in shows {
showsArray.append(show as! String)
}
//- When we've got our data ready, reload the table
self.MyTableView.reloadData()
self.refreshControl?.endRefreshing()
});
}
}
} catch {
print (error)
}
}

The snippet above should serve as a guide (I dont have access to a playground atm).

Note the following:
as soon as the task completes (asynchronously -> different thread) it will call the new function handleResult which will check for errors and if not, it will use the dispatcher to perform your task on the main thread. I'm assuming showsArrays is a class property.

I hope this helps

EDIT:

As soon as you fetch your data you need to reload the table (updated code above). You can use a refresh control (declare it as a class property).

var refreshControl: UIRefreshControl!

Then when you finish getting your data you can refresh:

self.MyTableView.reloadData()
self.refreshControl?.endRefreshing()

This will call your delegate methods to populate the rows and sections.



Related Topics



Leave a reply



Submit