I'm trying to retrieve some data from an URL thanks to JSON. Here's my swift code:
// get symbol asked
let symbol = symbolField.text!
// define URL
let url = NSURL(string: "http://yahoojson.gobu.fr/symbol.php?symbol=\(symbol)")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers)
print(jsonResult)
} catch {
print("Error JSON")
}
}
}
task.resume()
Everything seems to work fine, but the "do-try-catch" always prints "Error JSON". My code seems unable to convert my URL content into actual JSON. Any idea what I am doing wrong?
The URL returns html/javascript not pure json.
Paste the URL into your browser and look at the source code.
A side note: replace
print("Error JSON")
with
print(error)
to get more specific error information
Related
I am facing an issue when trying to retrieve and decode JSON data from an API. I am able to get the data from the API and decode it successfully, but I can't access it from outside the function scope. I am using the following function to get and decode data. The API returns an array of JSON objects.
func getJSON(completed: #escaping () -> ()) {
var jsonData = [API_data] () // A struct for retrieved data
let url = "URL Here" // I have the original URL here, which I can't share
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self]data,response,error in
guard let data = data, error == nil else{
print("error")
return
}
do{
jsonData = try JSONDecoder().decode([API_data].self,from:data)
} catch{
print(error)
}
print(jsonData[0].id) // This prints my data
})
print(jsonData[0].id) // This won't print my data
task.resume()
}
struct API_data: Codable {
let id : String
}
As a result of this, I cannot use this data anywhere in the application. Any help will be appreciated. I have also tried making jsonData a global variable, updating it in the function and returning it and then using it, still doesn't work.
Thanks for your help.
As you already have a completion handler use it and pass the received data
func getJSON(completed: #escaping ([API_data]) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { print(error); return }
do {
completed(try JSONDecoder().decode([API_data].self,from:data!))
} catch{
print(error)
}
}
task.resume()
}
and use it
getJSON { apiData in
print(apiData[0].id) // This prints my data
}
Or more comfortable with the Result type
func getJSON(completed: #escaping (Result<[API_data],Error>) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { completed(.failure(error)); return }
Result { try JSONDecoder().decode([API_data].self,from: data!) }
}
task.resume()
}
getJSON { result in
switch result {
case .success(let apiData): print(apiData[0].id) // This prints my data
case .failure(let error): print(error)
}
}
It is asynchronous, you might want to access it a bit later when it’s ready, e.g. by using the completion handler.
Try creating a completion handler from your API function and send the data with the completion. And try using it there.
I think this will be simple I assume I'm just missing something about JSON structure here. I have some code that pulls down some data from an API handle to get a list of country names:
https://restcountries.eu/rest/v2/all?fields=name
Here is a sample of the API data though please feel free to view it using the link above:
[{"name":"Afghanistan"},{"name":"Åland Islands"},{"name":"Albania"},{"name":"Algeria"},{"name":"American Samoa"},{"name":"Andorra"},{"name":"Angola"},{"name":"Anguilla"},{"name":"Antarctica"},{"name":"Antigua and Barbuda"},{"name":"Argentina"}
I created this struct to hold the data
struct CountryList: Codable {
public let country: [Country]
}
struct Country: Codable {
public let name: String
}
I have these two functions that create the URLRequest and then grab the data and return it via a completion handler:
private func setupApiUrlRequest(apiURL: String) throws -> URLRequest {
let urlString = apiURL
guard let url = URL(string: urlString) else {
print("Error setting up URL")
throw CountriesError.invalidURLString
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
return request
}
func getCountries(completion: #escaping (Country?, URLResponse?, Error?) -> Void) {
if let request = try? setupApiUrlRequest(apiURL: "https://restcountries.eu/rest/v2/all?fields=name") {
URLSession.shared.dataTask(with: request) { data,response,error in
guard let data = data else {
completion(nil, response, error)
return
}
do {
let decoder = JSONDecoder()
let downloadedCountries = try decoder.decode(Country.self, from: data)
completion(downloadedCountries, response, nil)
} catch {
print(error.localizedDescription)
completion(nil, response, error)
}
}.resume()
}
}
This gives me an error:
The data couldn’t be read because it isn’t in the correct format.
So it seems like my Struct is not correct somehow but I am just not sure how. Can anyone offer any guidance? I have a few other functions using almost identical code that grab API JSON Data and decode it into structs... just missing something here.
The JSON you've provided is not in the correct format.
Valid JSON:
[{"name":"Afghanistan"},{"name":"Åland Islands"},{"name":"Albania"},{"name":"Algeria"},{"name":"American Samoa"},{"name":"Andorra"},{"name":"Angola"},{"name":"Anguilla"},{"name":"Antarctica"},{"name":"Antigua and Barbuda"},{"name":"Argentina"}]
You need to use the [Country].self instead of just Country.self while parsing, i.e.
do {
let downloadedCountries = try JSONDecoder().decode([Country].self, from: data)
print(downloadedCountries)
} catch {
print(error)
}
Also, there is not requirement of struct CountryList. You can remove that.
My code return a Code=3840 "Garbage at end." when I try to keep my data of a request to my api ... The JSON return is a Valid JSON accorded to jsonlint (tested with Postman):
{
"error": 0,
"message": "transaction_completed"
}
this is my code :
func request(urle : url, parameters : Parameters, completion: #escaping (JSON) -> Void)
{
Alamofire.request(getUrl(urlw: urle), method: .post, parameters: parameters).responseJSON
{
response in
if response.data != nil {
do{
let json = try JSON(data: response.data!)
completion(json)
}catch{
print(error)
}
}
}
}
and this is when I called the request function:
let parameters: Parameters=[
"key" : user.key,
"uid": user.id
]
api.request(urle: .buyStack, parameters: parameters) { json in
print(json)
}
Where did I go wrong?
So apparently your JSON is not valid, it has at the end some invalid values.
First thing to do. For the sake of the keeping the logic, you can use force unwrap (using !) because it's debugging. I'm not sure that this code compile, it's just a logic presentation.
let responseString = String(data: response.data, encoding: .utf8)
print("responseString: \(responseString)")
This gives:
{"error":1,"message":"Undefined APIKey"}[]
There is extra [] at the end, and it's then not a valid JSON. You can ask the developper to fix it. If you really can't, or want to continue developing while it's in progress in their side, you can remove the extra [].
You can check this answer to remove the last two characters, and then:
let cleanResponseJSONString = //check the linked answer
let cleanResponseData = cleanResponseJSONString.data(encoding: .utf8)
let json = try JSON(data: cleanResponseData)
Side note and debugging idea if it was more complicate:
I ask for print("data: \(response.data as! NSData)") because this print the hex data. Your issue could have been due to an invisible character at the end. If you don't know them, the least you can do is according to previous answer:
let jsonString = "{\"error\":1,\"message\":\"Undefined APIKey\"}" (that's almost reponseString)
let jsonData = jsonString.data(encoding: .utf8)
print("jsonData: \(jsonData as! NSData)")
And compare what the end looks like.
A debugger tip, you can use a answer like this one to convert hexDataString into Data and debug from it. I'd recommend to add a space, "<" and ">" removal before so you can easily copy/paste it from the debugger output.
Why? If it's long (many manipulation) to go where your issue lies (login in the app, certain actions to do etc.), this could save you time to debug it on another app (Playground, at the start of your AppDelegate, etc.).
Don't forget to remove all the debug code afterwards ;)
Not related to the issue:
if response.data != nil {
do {
let json = try JSON(data: response.data!)
...
} catch {
...
}
}
Should be:
if let data = response.data {
do {
let json = try JSON(data: data)
...
} catch {
...
}
}
Use if let, guard let to unwrap, avoid using force unwrap.
I am trying to learn Swift. One of my projects is to try to retrieve JSON data from an internal web service (a group of Python CGI scripts) and convert it into a Swift object. I can do this easily in Python, but I am having trouble doing this in Swift. Here is my playground code:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
let endpoint: String = "http://pathToCgiScript/cgiScript.py"
let url = NSURL(string: endpoint)
let urlrequest = NSMutableURLRequest(URL: url!)
let headers: NSDictionary = ["User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)",
"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"]
urlrequest.allHTTPHeaderFields = headers as? [String : String]
urlrequest.HTTPMethod = "POST"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlrequest) {
(data, response, error) in
guard data != nil else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("Error calling script!")
print(error)
return
}
do {
guard let received = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as?
[String: AnyObject] else {
print("Could not get JSON from stream")
return
}
print(received)
} catch {
print("error parsing response from POST")
}
}
task.resume()
I know making a 'POST' to retrieve data may look odd, but that is how the system is set up. I keep on getting:
Could not get data from JSON
I checked the response, and the status is 200. I then checked the data's description with:
print(data?.description)
I got an unexpected result. Here is a snippet:
Optional("<0d0a5b7b 22535441 54555322 3a202244 6f6e6522 2c202242 55535922...
I used Mirror, and apparently the type is NSData. Not sure what to make of this. I have tried to encode the data with base64EncodedDataWithOptions. I have tried different NSJSONReadingOptions as well to no avail. Any ideas?
Update:
I used Wireshark to double check the code in the Playground. Not only was the call made correctly, but the data being sent back is correct as well. In fact, Wireshark sees the data as JSON. The issue is trying to turn the JSON data into a Swift object.
I figured out what was wrong. I was casting to the wrong type. This is the new code:
guard let received = try! NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? [AnyObject]
The JSON was not returning an array of dictionaries but an array of objects.
What is the best way to upload and download an image to Parse.com using REST API?
I tried to encode to base64, but can't seem to get it to cast back to UIImage I get the JSON NSData. Tried to decode back but didn't work.
After testing and extensive research, I found the solution:
After you cast the UIImage to NSData, you need to encode to base64 String and then upload the image to Parse.com using REST API see Using Swift in an iOS Application to Upload an Image to a RESTful API.
Next stage when you call a GET request to Parse.com, use the below to decode the base64 and cast it back to UIImage.
let task = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) in
if (error == nil) {
do {
let imageJSONDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject]
let base64String = imageJSONDictionary!["imageKey"] as! String
let imageData:NSData = NSData(base64EncodedString: base64String, options: NSDataBase64DecodingOptions(rawValue: 0))!
self.imageView.image = UIImage(data: imageData)
} catch {
}
} else {
print("ERROR: \(error)")
}
})
task.resume()