Alamofire JSON Request not pulling the data from a server - ios

Im using Alamofire to pull a JSON file from a server (http://midlandgates.co.uk/JSON/reliveCacheData.json). To do this I have an Alamofire Request function which should pull the data down and write it to a class called JSONDataClass.
Alamofire Request
Alamofire.request(.GET, "http://midlandgates.co.uk/JSON/reliveCacheData.json")
.response { request, response, data, error in
if let data = data {
let json = JSON(data:data)
for locationData in json {
let locationDataJSON = JSONDataClass(json: locationData.1)
self.cacheData.append(locationDataJSON)
}
for title in self.cacheData {
print(title.memoryTitle)
}
}
}
However my printLn isn't printing the data from the JSON File which indicates there is a problem with either the request or the class. However I'm new to swift and can't seem to work out the problem, all help is appreciated!
Class
import Foundation
import CoreLocation
import SwiftyJSON
class JSONDataClass {
//Location MetaData
var postUser: String!
var memoryTitle: String!
var memoryDescription: String!
var memoryType: String!
var memoryEmotion: String!
var taggedFriends: String!
//Location LocationalData
var memoryLocation: CLLocationCoordinate2D
//MultiMedia AddressData
var media1: String!
var media2: String!
var media3: String!
var media4: String!
var media5: String!
//Writing to varibles
init(json: JSON) {
postUser = json["user"].stringValue
memoryTitle = json["title"].stringValue
memoryDescription = json["description"].stringValue
memoryType = json["type"].stringValue
memoryEmotion = json["emotion"].stringValue
taggedFriends = json["friends"].stringValue
memoryLocation = CLLocationCoordinate2D(latitude: json["latitude"].doubleValue, longitude: json["longitude"].doubleValue)
media1 = json["media1"].stringValue
media2 = json["media2"].stringValue
media3 = json["media3"].stringValue
media4 = json["media4"].stringValue
media5 = json["media5"].stringValue
}
}

(Note: the author of the JSON content has fixed his JSON based on my answer - my answer was correct at the time of this post).
The problem is that you've got invalid JSON coming back from the server.
I switched your SwiftyJSON code out and wrapped it in a try-catch which illuminated the problem:
Alamofire.request(.GET, "http://midlandgates.co.uk/JSON/reliveCacheData.json")
.response { request, response, data, error in
//print("status \(response)")
do {
let myData = try NSJSONSerialization.JSONObjectWithData(data!,
options: .MutableLeaves)
print(myData)
} catch let error {
print(error)
}
}
And I get the following error:
2015-12-30 13:47:06.129 {{ignore}}[10185:2174510] Unknown class log in Interface Builder file.
Error Domain=NSCocoaErrorDomain Code=3840 "Unescaped control character around character 961."
UserInfo={NSDebugDescription=Unescaped control character around
character 961.}
This error basically means that you have invalid JSON coming back from the server.
Make sure your json is valid by running it through JSONLint:
http://jsonlint.com

Are you new to debuggers and breakpoints?
Set a breakpoint in your response closure (for example, in your if let data = data { line) and step through your code to see if it does what you expect at every stage. Maybe error is set? Maybe the if let data = data { conditional failed (you're not doing anything in an else to log the failure, so how could you know?)...

After I checked the discussion and the data provided by the server from you link, I can say, that your trouble is how to deal with your JSON (class, or struct, or enum??)
Did you follow some tutorial from SwiftyJSON? While your init(json: JSON) in class JSONDataClass seems to accept some JSON object (aka dictionary), in reality your json is an JSON array! Check SwiftyJSON documentation and take in your account, that you received an array.
try this instead:
for d in json {
let locationDataJSON = JSONDataClass(json: d)
self.cacheData.append(locationDataJSON)
}
and see, that locationDataJSON is NOT location data but one json object from the received array. or just remove .1 in locationData.1
For example:
Alamofire.request(.GET, "http://midlandgates.co.uk/JSON/reliveCacheData.json")
.response { request, response, data, error in
if let data = data {
let json = JSON(data:data)
for locationData in json {
let locationDataJSON = JSONDataClass(json: locationData)
self.cacheData.append(locationDataJSON)
}
for title in self.cacheData {
print(title.memoryTitle)
}
}
}

Related

Swift dispatch queue block not running

I am currently writing an Alamofire HTTP request and am running into an issue where my view is not loading - likely because there is no data. The confusing part is that this was working yesterday. In the request I was able to do print(data) and the result was 506 bytes which, if my calculation is correct, is about the correct size given the JSON payload returned from the endpoint below.
#State var recipes = [Recipe]()
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else { return }
if let response = try? JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
return
}
}
I can confirm that the endpoint that is being hit returns the following data...
[
{
"name":"Manhattan",
"image":"https://images.unsplash.com/photo-1536935338788-846bb9981813?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2486&q=80",
"spirit":"Bourbon",
"ice":"Crushed",
"glass":"Coupe",
"yield":"3.75",
"description":"This is a good drink. You should make it.",
"ingredients":[
{
"bottle":"High West Son Of Bourye",
"amount":"2.5"
},
{
"bottle":"Cocchi Vermouth Di Torino",
"amount":"0.75"
},
{
"bottle":"Simple Syrup",
"amount":"0.083"
}
]
}
]
I also have my Recipe and Ingredient model here which should be able to decode based on the above JSON.
struct Recipe: Decodable, Identifiable {
var id = UUID()
var name: String
var image: String
var spirit: String
var ice: String
var glass: String
var yield: String
var description: String
var ingredients: [Ingredient]
}
struct Ingredient: Decodable, Identifiable {
var id = UUID()
var bottle: String
var amount: String
}
Is anybody able to spot an issue? I was trying to put a debugging print in the DispatchQueue but it is not printing which, to me, sounds like an error. However I am new to Swift/XCode/iOS and am not sure the best debugging practices for this.
If you can't debug yourself, NEVER USE try?. With more experience, I'd say that we tend to not use try?, but sometimes we do. But when we write try?, we are able to find an possible issue, ie debug if needed.
Let's do a proper try then, with a do/catch:
do {
let response = try JSONDecoder().decode([Recipe].self, from: data
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
}
And read the output.
Going further?
Don't believe what's the API is supposed to return.
I've seen plenty and plenty of questions where the returned values was an error message, a XML Error message, a JSON Error message, an HTML Error message, and a JSON value missing, or of bad type, etc. And that, your JSONDecoder wasn't expecting it...
Reasons could be various, from bad/missing parameters, bad/missing APIKey, server down, bad/missing header, etc.
But, then, print the returned value.
print(String(data: data, encoding: .utf8) ?? "No data found")
So print it directly when you get it, or at least in the catch:
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
print("While getting response stringified: \(String(data: data, encoding: .utf8) ?? "No data found")")
}
If you don't understand the error message output, it's okay, there is no shame about it. But your first job is to get that error message. You can share it on SO if you don't understand it, you might get help with that. But currently, we can't guess what's wrong with your code.
It's a good idea to drop some clues in your code when looking for a failure.
If it were me I'd do something like this:
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else {
print("Error trying to receive data in ", #file, #function)
return
}
do {
let response = try JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Error failed to decode json data with error: \(error) in \(#file)", #function)
}
}

use of "po" to get data from server response in console log

i know this is very basic question that i am gonna ask but i need to ask how can i access data which is dictionary that is getting from server.
here is my response
JSON: {
message = "The email must be a valid email address.";}
now i want to do "po" in console log so what to write after in po statement
Thanks
All you need type
po yourResponseAsDictionary["message"]
UPDATED
Sorry I was thinking you already converted it.
If you are not using anything like SwiftyJSON or ObjectMapper to parse your json data then you can do just the following.
But I would recommend to use some lib to convert response directly to your model
let yourResponseFromNetwork = "{ \"country_code\" : \"+9\", \"user_id\" : 123456}"
if let data = yourResponseFromNetwork.data(using: String.Encoding.utf8) {
do {
if let dic = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] {
let countryCode = dic["country_code"]
let userId = dic["user_id"]
}
} catch {
print("Error occurred")
}
}

How to remove xml tags before send to the json parser in swift

I want to use JSON data in my app. So I am using this webservice calling method to convert my json data to an array.
func getData(path: String, completion: (dataArray: NSArray)->()) {
let semaphore = dispatch_semaphore_create(0)
// var datalistArray = NSArray()
let baseUrl = NSBundle.mainBundle().infoDictionary!["BaseURL"] as! String
let fullUrl = "\(baseUrl)\(path)"
print("FULL URL-----HTTPClient \(fullUrl)")
guard let endpoint = NSURL(string:fullUrl) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(URL: endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: {(data,response,error) in
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSArray else {//NSJSONReadingOptions.AllowFragments
throw JSONError.ConversionFailed
}
print(json)
if let data_list:NSArray = json {
completion(dataArray: data_list)
dispatch_semaphore_signal(semaphore);
}
}catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}) .resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
But now my service sending json data within xml tags like <string xmlns="http://tempuri.org/">json data</string so I am getting an exception when I try to convert my json data. The exception is this.
Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}
What should I change in my code to remove those tags before sending to json parser?
Please help me.
Thanks
I think your response that you get from server is in xml format not in json. If it is in xml format then you must do xml parsing instead of json parsing.
NSJSONSerialization.JSONObjectWithData is json parsing that give json object from data (data in json format).
But if you getting response in xml format from server then you should use NSXMLParser to parse the data.
If you don't have much idea about it then you can refer tutorial like
XML Parsing using NSXMLParse in Swift by The appguruz or can use third party libraries.

How can I process multiple links of JSON data?

The code works perfectly. The problem is that, after trying for a while, I cannot figure out how to make my program process a second link of different JSON data.
Here is my viewDidLoad where everything goes on:
override func viewDidLoad() {
super.viewDidLoad()
var err: NSError?
let urlPath: String = "https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/" + searchFieldDataPassed + "?api_key=(removed my private api key for obvious reasons"
var url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// cast response as NSHTTPURLResponse and switch on statusCode if you like
if let httpResponse = response as? NSHTTPURLResponse { switch httpResponse.statusCode { case 200..<300: println("OK") default: println("Not OK") } }
// parse JSON using NSJSONSerialization if you've got data
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary,
let include = jsonResult.objectForKey(self.searchFieldDataPassed) as? NSDictionary {
if let summLevel = include[ "summonerLevel" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.summonerLevel.text = "\(summLevel.integerValue)"
println("summoner level: \(summLevel.integerValue)")
}
}
if let profIconId = include[ "profileIconId" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.profileIconId.text = "\(profIconId.integerValue)"
println("profile icon id: \(profIconId.integerValue)")
}
}
if let idNum = include [ "id" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.idNumber = idNum
println("id number: \(self.idNumber)")
}
}
}
// spawn off another network call here if you like
}
task.resume()
}
That is from my secondViewController where all the processing goes on for JSON and then is displayed.
Here is the JSON data that I'm processing (for the first JSON parsing):
{"soon2challenger":{"id":43993167,"name":"soon2challenger","profileIconId":844,"summonerLevel":30,"revisionDate":1435549418000}}
All of that works fine, now, I want to process this JSON data which actually takes the id from the first parsed JSON data and uses it in the link to process more data, which I would like to output, part of it, to the screen.
Second JSON data:
{"summonerId":43993167,"playerStatSummaries":[{"playerStatSummaryType":"AramUnranked5x5","wins":25,"modifyDate":1423007927000,"aggregatedStats":{"totalChampionKills":676,"totalTurretsKilled":20,"totalAssists":991}},{"playerStatSummaryType":"CAP5x5","wins":15,"modifyDate":1429065922000,"aggregatedStats":{"totalChampionKills":312,"totalMinionKills":4885,"totalTurretsKilled":31,"totalNeutralMinionsKilled":511,"totalAssists":216}},{"playerStatSummaryType":"CoopVsAI","wins":28,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":266,"totalMinionKills":2802,"totalTurretsKilled":50,"totalNeutralMinionsKilled":385,"totalAssists":164,"maxChampionsKilled":0,"averageNodeCapture":0,"averageNodeNeutralize":0,"averageTeamObjective":0,"averageTotalPlayerScore":49,"averageCombatPlayerScore":0,"averageObjectivePlayerScore":49,"averageNodeCaptureAssist":0,"averageNodeNeutralizeAssist":0,"maxNodeCapture":0,"maxNodeNeutralize":0,"maxTeamObjective":0,"maxTotalPlayerScore":49,"maxCombatPlayerScore":0,"maxObjectivePlayerScore":49,"maxNodeCaptureAssist":0,"maxNodeNeutralizeAssist":0,"totalNodeNeutralize":0,"totalNodeCapture":0,"averageChampionsKilled":0,"averageNumDeaths":0,"averageAssists":0,"maxAssists":0}},{"playerStatSummaryType":"CoopVsAI3x3","wins":15,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":140,"totalMinionKills":1114,"totalTurretsKilled":9,"totalNeutralMinionsKilled":449,"totalAssists":91}},{"playerStatSummaryType":"OdinUnranked","wins":1,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":31,"totalAssists":45,"maxChampionsKilled":10,"averageNodeCapture":4,"averageNodeNeutralize":4,"averageTeamObjective":0,"averageTotalPlayerScore":843,"averageCombatPlayerScore":268,"averageObjectivePlayerScore":575,"averageNodeCaptureAssist":3,"averageNodeNeutralizeAssist":1,"maxNodeCapture":6,"maxNodeNeutralize":7,"maxTeamObjective":2,"maxTotalPlayerScore":1468,"maxCombatPlayerScore":529,"maxObjectivePlayerScore":939,"maxNodeCaptureAssist":5,"maxNodeNeutralizeAssist":2,"totalNodeNeutralize":22,"totalNodeCapture":25,"averageChampionsKilled":5,"averageNumDeaths":5,"averageAssists":8,"maxAssists":19}},{"playerStatSummaryType":"RankedSolo5x5","wins":116,"losses":120,"modifyDate":1433630047000,"aggregatedStats":{"totalChampionKills":1699,"totalMinionKills":33431,"totalTurretsKilled":219,"totalNeutralMinionsKilled":6501,"totalAssists":1969}},{"playerStatSummaryType":"RankedTeam3x3","wins":0,"losses":0,"modifyDate":1377726216000,"aggregatedStats":{}},{"playerStatSummaryType":"RankedTeam5x5","wins":3,"losses":0,"modifyDate":1383784473000,"aggregatedStats":{"totalChampionKills":28,"totalMinionKills":636,"totalTurretsKilled":6,"totalNeutralMinionsKilled":101,"totalAssists":41}},{"playerStatSummaryType":"Unranked3x3","wins":9,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":90,"totalMinionKills":1427,"totalTurretsKilled":11,"totalNeutralMinionsKilled":428,"totalAssists":105}},{"playerStatSummaryType":"URF","wins":4,"modifyDate":1435024847000,"aggregatedStats":{"totalChampionKills":68,"totalMinionKills":642,"totalTurretsKilled":14,"totalNeutralMinionsKilled":182,"totalAssists":55}},{"playerStatSummaryType":"Unranked","wins":566,"modifyDate":1435549418000,"aggregatedStats":{"totalChampionKills":8419,"totalMinionKills":128213,"totalTurretsKilled":960,"totalNeutralMinionsKilled":26117,"totalAssists":7812}}]}
Heres the link of the second JSON data I want to parse (just adding it, could be useful, but not sure):
https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/43993167/summary?season=SEASON2015&api_key=(took-out-my-private-api-key-for-obvious-reasons)
The link doesn't work because I have to keep my api key private to myself, but the JSON data that it displays is right above the link, which is the what it would result if you were to use the link with the api key.
Just to restate, I would like to process the second part (above of this) of JSON data, but I do not understand how to process multiple links of JSON. I have the first JSON data parsed, but am unable to parse the second JSON data.
I believe Apple is deprecating NSURLConnection. Take a look at NSURLSession. Using it, you can pass in a completion block that takes three arguments: NSData?, NSURLResponse?, and NSError?. The data object contains the JSON you can pass into the JSON serializer. After that, if you need to make another network call, just call it from inside the completion block with another NSURLSession data task. Alamofire is a great framework, but sometimes you don't need everything it provides, and it adds complexity into your app that if something goes wrong or doesn't behave the way you intend/understand, you may not fully understand why. If you want to keep it simple and under your control, use NSURLSession.
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// cast response as NSHTTPURLResponse and switch on statusCode if you like
// parse JSON using NSJSONSerialization if you've got data
// spawn off another network call here if you like
}
task.resume() // or in Swift 2, task?.resume()
First, i would totally prefer using some common frameworks for http requests - expecially if youre new in swift. For example here with alamofire.
https://github.com/Alamofire/Alamofire
There is also a version with integrated SwiftyJSON, so you are able to parse JSON Responses very easily.
https://github.com/SwiftyJSON/Alamofire-SwiftyJSON
So if you want to make a request, use this:
Alamofire.request(.GET, "http://httpbin.org/get")
.responseJSON { (_, _, json, _) in
var json = JSON(json)
// get the id out (depends on your structure of JSON):
let id = json["id"].int
}
Now you are able to perform a second Request (with the same Code) - Read the Documentation, how to make different Requests (like with POST) and add Parameters.
If you want to use Segues, so you want to load more data from the ID in another ViewController, you can use Segues to push the data to a second ViewController, and Load the new Content from JSON when the new ViewController is initialised.
Check out this how to send data through segues:
Sending data with Segue with Swift

How can I use swiftyJSON dictionaryValue as a usable string for a UILabel?

I have a makeRequest() method inside a UITableViewController with the following code:
func makeRequest() {
Alamofire.request(.GET, self.foursquareEndpointURL, parameters: [
//"VENUE_ID" : self.foursquareVenueID,
"client_id" : self.foursquareClientID,
"client_secret" : self.foursquareClientSecret,
"v" : "20140806"
])
.responseJSON(options: nil) { (_, _, data, error) -> Void in
if error != nil {
println(error?.localizedDescription)
} else if let data: AnyObject = data {
let jObj = JSON(data)
if let venue = jObj["response"]["venue"].dictionaryValue as [String: JSON]? {
self.responseitems = jObj
println("venue is: \(venue)")
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData() // Update UI
}
}
}
}
also keep in mind that I have a property var responseitems:JSON = []
println("venue is: \(venue)") prints a nice looking response to console, so I know that is working correctly...
I also have a custom UITableViewCell class with a bindData() method with the following code:
func bindData() {
println("VenueDetailHeaderCell data did set")
self.venueDetailTitleLabel.text = self.headerInfo?["name"].stringValue
let labelData = self.headerInfo?["name"].stringValue
println("labelData is: \(labelData)")
}
As you can see, I am attempting to set a UILabel's text to the ["name"].stringValue in the JSON response. However, when I println("labelData is: \(labelData)") I get console output of labelData is: Optional("") which is obviously empty.
Here's a screenshot of what I'm trying to grab
What am I doing wrong here and how can I grab the name of the venue and assign my UILabel to it?
UPDATE:
I tried the following code
let labelData = self.headerInfo?["name"].error
println("labelData is: \(labelData)")
And get a console output of: "Error Domain=SwiftyJSONErrorDomain Code=901 "Array[0] failure, It is not an array" UserInfo=0x7fd6d9f7dc10 {NSLocalizedDescription=Array[0] failure, It is not an array}" If that is of use to anyone. I am really confused here... Any ideas?
The problem is that the headerInfo value is an error JSON object, because you're trying to access a dictionary with an integer index.
Note that var responseitems:JSON = [] does not create an array object. SwiftyJSON has auto-assignment-constructors (I'm new to swift, so not sure what the correct swift terminology is)... see this initialiser in the SwiftyJSON.swift source code:
extension JSON: ArrayLiteralConvertible {
public init(arrayLiteral elements: AnyObject...) {
self.init(elements)
}
}
What this means is that when you do var responseitems:JSON = [] you are not creating an array, you are creating a JSON object that is constructed with an empty array using the above init method. Then when you do self.responseitems = jObj you are re-assigning that responseitems variable to a JSON object with a dictionary in it. Therefore self.responseitems[0] is invalid.
Also note that with SwiftyJSON, there is no such thing as an optional JSON object. I notice in your comment you say that you do var headerInfo:JSON? ... - it's not possible to have an optional JSON.
var headerInfo: JSON = nil
The above is possible - this uses another auto-initialiser that initialises a valid JSON object that represents the JSON null value.
So, how to fix it?
When you assign headerInfo do it like this:
let headerInfo = self.responseitems["response"]["venue"]
And now in bindData you can do:
self.venueDetailTitleLabel.text = self.headerInfo["name"].stringValue
Note that all of the above assumes Swift 1.2 and SwiftyJSON >= 2.2 - also after you've understood the above and corrected the issue, you will probably want to refactor the code a bit to reflect the corrected understanding of the data-model.
The way to parse is the next
element: JSONValue //Something with JSONValue
if let title = element["name"]?.string {
cell.title.text = title
}

Resources