Swift - How To Error Check JSON File? - json

How would I be able to check whether or not the downloaded JSON content contains an error message rather than the expected content? I've tried to validate the URL but that cannot work due to how a false subdomain (location in this case) still returns an Error Message through the JSON content. I'd appreciate it if anybody could help me out. (Note: I want to check for an invalid location entered by the user and I'm using OpenWeatherMap API.)
func downloadData(completed: #escaping ()-> ()) {
print(url)
//UIApplication.shared.openURL(url as URL)
Alamofire.request(url).responseJSON(completionHandler: {
response in
let result = response.result
if let dict = result.value as? JSONStandard, let main = dict["main"] as? JSONStandard, let temp = main["temp"] as? Double, let weatherArray = dict["weather"] as? [JSONStandard], let weather = weatherArray[0]["main"] as? String, let name = dict["name"] as? String, let sys = dict["sys"] as? JSONStandard, let country = sys["country"] as? String, let dt = dict["dt"] as? Double {
self._temp = String(format: "%.0f °F", (1.8*(temp-273))+32)
self._weather = weather
self._location = "\(name), \(country)"
self._date = dt
}
completed()
})
}

Assuming the resulting JSON has different content when there is an error, check dict for the error content. Below is an example assuming there is a key named error. Adjust as needed based on what you really get when there is an error.
Alamofire.request(url).responseJSON(completionHandler: {
response in
let result = response.result
if let dict = result.value as? JSONStandard {
if let error = dict["error"] {
// parse the error details from the JSON and do what you want
} else if let main = dict["main"] as? JSONStandard, let temp = main["temp"] as? Double, let weatherArray = dict["weather"] as? [JSONStandard], let weather = weatherArray[0]["main"] as? String, let name = dict["name"] as? String, let sys = dict["sys"] as? JSONStandard, let country = sys["country"] as? String, let dt = dict["dt"] as? Double {
self._temp = String(format: "%.0f °F", (1.8*(temp-273))+32)
self._weather = weather
self._location = "\(name), \(country)"
self._date = dt
} else {
// Unexpected content, handle as needed
}
}
completed()
})
You should also provide a parameter to your downloadData completion handler so you can pass back an indication of success or failure so the caller can handle the result appropriately.

Related

How can I restore my old task with a new one in swift?

I have question about tasks. I need to restore my request, I change link and fetch some new data to that link and show them on my table view. User can change link according to picker view, I have a variable for it and I replaced in link and thats working correct too but in second request can make a thread I assume it is very bad question but I am new in swift. How can I make second or more request in one function.
Here my function code :
func fetchArticles(){
let urlRequest = URLRequest(url: URL(string: "my_api_link_is_here")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (Data,URLResponse,Error) in
if Error != nil {
print(Error!)
}
self.articles = [Article]()
do{
let json = try JSONSerialization.jsonObject(with: Data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String:AnyObject]] {
for articleFromJson in articlesFromJson {
let article = Article()
let source = articleFromJson["source"] as![String: AnyObject]
let name = source["name"]
if let title = articleFromJson["title"] as? String, let author = name as? String , let desc = articleFromJson["description"] as? String, let url = articleFromJson["url"] as? String, let imageToUrl = articleFromJson["urlToImage"] as? String {
article.author = author as String
if articleFromJson.index(forKey: "description") != nil {
article.desc = desc as String
}else{
article.desc = "empty"
}
article.headline = title as String
article.imageUrl = imageToUrl as String
article.url = url as String
}
self.articles?.append(article)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}catch let Error {
print(Error)
}
}
task.resume()
}

Xcode 9 Swift 4 Complex JSON decoding

I am working with API data that returns JSON data that is hard to decode. The api call is for a batch of stock quotations. When a single quotation (not batch) is called, the result is easily decoded JSON using a simple struct. However, in batch mode the single quote version is grouped within two more levels that I can not decode. In the interest of making this easy to read I will just paste the initial pieces of the data in order to illustrate the issue.
The single quote JSON:
{"symbol":"AAPL","companyName":"Apple Inc.","primaryExchange":"Nasdaq Global Select",
So, that's easy... key, value pairs from the start but in batch mode this becomes:
{"AAPL":{"quote":{"symbol":"AAPL","companyName":"Apple Inc.","primaryExchange":"Nasdaq Global Select",
and then later in that same result would be a second or third or more quote, eg.
}},"FB":{"quote":{"symbol":"FB","companyName":"Facebook Inc.","primaryExchange":"Nasdaq Global Select",
So at the highest level it is not a key but is instead a value. And the second level is a metadata type placeholder for quote (because you can also request other subelement arrays like company, charts, etc.) I can't think of how to handle the outer grouping(s) especially the stock symbols AAPL and FB ... as the outermost elements. Any thoughts anyone?
I have started down the path of JSONSerialization which produces a string that I also cannot get into a usable form.
For this I am using:
let tkrs = "C,DFS"
var components = URLComponents()
components.scheme = "https"
components.host = "api.iextrading.com"
components.path = "/1.0/stock/market/batch"
let queryItemSymbols = URLQueryItem(name: "symbols", value: "\(tkrs)")
let queryItemTypes = URLQueryItem(name: "types", value: "quote")
components.queryItems = [queryItemSymbols,queryItemTypes]
let session = URLSession.shared
let task = session.dataTask(with: components.url!) {(data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
print(json)
which produces:
["C": {
quote = {
avgTotalVolume = 17386485;
calculationPrice = tops;
change = "1.155";
changePercent = "0.0181";
close = "63.8";
closeTime = 1540411451191;
companyName = "Citigroup Inc.";
and there is more data but I'm clipping it short.
The api url's are:
single quote example:
https://api.iextrading.com/1.0/stock/aapl/quote
batch quote example:
https://api.iextrading.com/1.0/stock/market/batch?symbols=aapl,fb&types=quote
The struct I have used successfully for the single quote works nicely with a simple line of code:
let quote = try JSONDecoder().decode(Quote.self,from: data)
where Quote is a struct:
struct Quote: Decodable {
let symbol: String
let companyName: String
let primaryExchange: String
let sector: String
let calculationPrice: String
let open: Double
let openTime: Int
let close: Double
let closeTime: Int
let high: Double
let low: Double
let latestPrice: Double
let latestSource: String
let latestTime: String
let latestUpdate: Int
let latestVolume: Double
let iexRealtimePrice: Double?
let iexRealtimeSize: Double?
let iexLastUpdated: Int?
let delayedPrice: Double
let delayedPriceTime: Int
let extendedPrice: Double
let extendedChange: Double
let extendedChangePercent: Double
let extendedPriceTime: Int
let previousClose: Double
let change: Double
let changePercent: Double
let iexMarketPercent: Double?
let iexVolume: Double?
let avgTotalVolume: Double
let iexBidPrice: Double?
let iexBidSize: Double?
let iexAskPrice: Double?
let iexAskSize: Double?
let marketCap: Double
let peRatio: Double?
let week52High: Double
let week52Low: Double
let ytdChange: Double
}
Edit: based on answer provided
Working in a playground this works well with the batch data:
func getPrices(){
let tkrs = "AAPL,FB,C,DFS,MSFT,ATVI"
var components = URLComponents()
components.scheme = "https"
components.host = "api.iextrading.com" ///1.0/stock/market/batch
components.path = "/1.0/stock/market/batch"
let queryItemSymbols = URLQueryItem(name: "symbols", value: "\(tkrs)")
let queryItemTypes = URLQueryItem(name: "types", value: "quote")
components.queryItems = [queryItemSymbols,queryItemTypes]
let data = try! Data(contentsOf: components.url!)
do {
let response = try JSONDecoder().decode([String:[String: Quote]].self,from: data)
let tickers = ["AAPL","FB","C","DFS","MSFT","ATVI"]
for tk in tickers {
let quote = response[tk]
let price = quote!["quote"]
print("\(price!.symbol) \(price!.latestPrice)")
}
} catch let jsonErr { print("Error decoding json:",jsonErr)}
}
But this solves my initial problem of getting a response back from a URLSession for just a single quote. I can now run through an array of stock symbols and update the latest price for each item with this function.
func getPrice(ticker: String) -> Double {
var price = 0.0
let urlString = "https://api.iextrading.com/1.0/stock/\(ticker)/quote"
let data = try! Data(contentsOf: URL(string: urlString)!)
do {
let response = try JSONDecoder().decode(Quote.self,from: data)
price = response.latestPrice
} catch let jsonErr { print("Error decoding JSON:",jsonErr)}
return price
}
So, I am iterating through an array of open stock trades and setting the price like this...
opentrades[rn].trCurPrice = getPrice(ticker: opentrades[rn].trTicker)
And it works great in my application. Although I am a little worried about how it will workout during times of high latency. I realize I need some error control and will work to integrate that going forward.
Edit/Update: Based on feedback here is the approach I'm taking.
Created a class to be a delegate that accepts an array of open trades and updates the prices.
import Foundation
protocol BatchQuoteManagerDelegate {
func didLoadBatchQuote()
}
class BatchQuoteManager {
var openPositions = [OpenTradeDur]()
var delegate: BatchQuoteManagerDelegate? = nil
func getBatchQuote(tickers: [OpenTradeDur]) {
var tkrs = ""
for tk in tickers {
tkrs = tkrs + "\(tk.trTicker),"
}
var components = URLComponents()
components.scheme = "https"
components.host = "api.iextrading.com"
components.path = "/1.0/stock/market/batch"
let queryItemSymbols = URLQueryItem(name: "symbols", value: "\(tkrs)")
let queryItemTypes = URLQueryItem(name: "types", value: "quote")
components.queryItems = [queryItemSymbols,queryItemTypes]
let session = URLSession.shared
let task = session.dataTask(with: components.url!) {(data,response,error) in
guard let data = data, error == nil else { return }
let response = try! JSONDecoder().decode([String:[String: Quote]].self,from: data)
for i in 0..<tickers.count {
let quote = response[tickers[i].trTicker]
let price = quote!["quote"]
tickers[i].trCurPrice = price!.latestPrice
}
self.openPositions = tickers
if let delegate = self.delegate {
DispatchQueue.main.async {
delegate.didLoadBatchQuote()
}
}
}
task.resume()
}
}
I then extend my ViewController with BatchQuoteManagerDelegate, implement the func didLoadBatchQuote() method where I get the updated prices via the BatchQuoteManager.openPositions array. I just needed to define let batchQuoteManager = BatchQuoteManager() in my ViewController and within viewDidLoad() include the statement batchQuoteManager.delegate = self. Once I know that all the necessary data has been loaded into my ViewController I call the function to get prices (at the end of viewDidLoad()) with batchQuoteManager.getBatchQuote(tickers: opentrades)
And that's it. It is working very nicely so far.
The Dictionary type conditionally conforms to Decodable if its associated KeyType and ValueType conform to Decodable. You can decode the whole Dictionary.
let response = try JSONDecoder().decode([String:[String: Quote]].self,from: data)
let apple = response["AAPL"]
let appleQuote = apple["quote"]
Try this gist in a playground
https://gist.github.com/caquant/eeee66b7b8df447c4ea06b8ab8c1116a
Edit: Here is a quick example with URLSession
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else { return }
let response = try! JSONDecoder().decode([String:[String: Quote]].self,from: data)
let apple = response["FB"]
let appleQuote = apple!["quote"]
print(appleQuote!)
}
dataTask.resume()
Note: The gist was also updated.

How to get the multiple data inside the json curly braces? Swift

How to get the multiple data inside the json curly braces in swift3?
Can i use this code to get multiple data? (get "crew_id","crew_name","crew_email")
if let crew = user!["crew"] as? [String:Any], let crewName = crew["crew_name"] as? String {
print(crewName)
JSON
crew ={
"crew_avatar" = "http://ec2-52-221-231-3.ap-southeast-1.compute.amazonaws.com/gv/images/profile_image/Pang_Kang_Ming_916210_0e9.jpg";
"crew_contact" = 0123456789;
"crew_email" = "pang#xover.com.my";
"crew_gender" = Male;
"crew_id" = PP000001;
"crew_name" = "Pang Kang Ming";
"crew_preferred_name" = PKM;
"crew_qrcode" = "images/qrcode/qrcode_085960293a5378a64bec6ebfa3c89bb7.png";
};
message = "Login Sucessfully";
result = success;
Yes you can, just add the values you want to unwrap as below, just be aware that if one of the optional binding does not unwrap, even if others unwrap if statement will not be executed, consider separating the if statements.
Depends on all being returned in the json.
if let crew = user!["crew"] as? [String:Any],
let crewName = crew["crew_name"] as? String,
let crewId = crew["crew_id"] as? String {
print(crewName)
print(crewId)
}
Recommended way, even if some values are not present in the json response, you will be able to get the other values.
if let crew = user!["crew"] as? [String:Any] {
if let crewName = crew["crew_name"] as? String {
print(crewName)
}
if let crewId = crew["crew_id"] as? String {
print(crewId)
}
}
if let file = Bundle.main.url(forResource: "yourJsonFileName", withExtension: "json") {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data, options: [])
let jsonData = json as! [[String:Any]]
DispatchQueue.main.async {
let projectName = jsonData.flatMap { $0["crew_avatar"] as? String }
self.crewAvatarArray = projectName
print(self.crewAvatarArray)
let subTitle = jsonData.flatMap { $0["crew_contact"] as? String }
self.crewContactArray = subTitle
let startDate = jsonData.flatMap { $0["crew_email"] as? String }
self.crewEmailArray = startDate
}
}
Try this code

Ambiguous use go 'subscript'

I am trying to get data that has been encoded as json in a php script.
My code:
func getJson(completion: #escaping (Array<CarPark>) -> Void) {
activityIndicatior.center = self.view.center
activityIndicatior.hidesWhenStopped = true
activityIndicatior.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
view.addSubview(activityIndicatior)
self.activityIndicatior.startAnimating()
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("ERROR")
}
else
{
if let content = data
{
do{
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
for index in 0..<myJson.count {
if let entry = myJson[index] as? NSDictionary{
let name = entry["Name"] as! String
let longitude = Double(entry["Longitude"] as! String)
let latitude = Double(entry["Latitude"] as! String)
let quiet = Int(entry["Quiet"] as! String)
let moderate = Int(entry["Moderate"] as! String)
let busy = Int(entry["Busy"] as! String)
let coordinate = CLLocationCoordinate2D( latitude: latitude!, longitude: longitude!)
print("coordinate lat is : \(coordinate.latitude)")
print("coordinate long is : \(coordinate.longitude)")
print("coordinate full is: \(coordinate)")
let tempPark = CarPark(name: name, latitude: latitude!, longitude: longitude!, quiet: quiet!, moderate: moderate!, busy: busy!, coordinate: coordinate, level: "Nil")
let level = tempPark.calcAvailability()
tempPark.level = level
print("Availability is \(tempPark.level)")
self.tempCarParks.append(tempPark)
// print("amount of parks: \(self.carParks.count)")
print("name of parks in array: \(self.tempCarParks[index].name)")
print("Availability is \(tempPark.level)")
}
}
completion(self.tempCarParks)
}
catch
{
print("Error")
}
}
}
}
task.resume()
}
I am getting an error that says 'Ambiguous use of subscript' at the line:
if let entry = myJson[index] as? NSDictionary{
How can I fix this?
Since you know myJson is an array why do you cast the object to unspecified AnyObject?
That causes the error, the compiler needs to know the concrete types of all subscripted objects. Help the compiler then the compiler helps you:
let myJson = try JSONSerialization.jsonObject(with: content) as! [[String:Any]]
for entry in myJson { // don't use old-fashioned index based loops
let name = entry["Name"] as! String
...
.mutableContainers is completely useless in Swift.

JSON unwrap not running

I have a JSON fetch happening and then I do stuff with the data. My JSONObject is created and then I go about working with the data. A sample can be seen here: https://openlibrary.org/api/books?bibkeys=1593243987&f&jscmd=data&format=json
My first block to extract the author name is working perfectly, however the second to extract the cover url as a string isn't even running and I have no idea why.
If I set a breakpoint at if let thumbs = bookDictionary["cover"] as? NSArray {it stops, but then when I 'step through' the code, it jumps to the end and moves on, not even running anything inside the block.
I would appreciate any help anyone can offer. I'm using Swift 2.0 / Xcode 7b6.
let requestURL = ("https://openlibrary.org/api/books?bibkeys=" + lookUpID + "&f&jscmd=data&format=json")
let url = NSURL(string: requestURL)
let req = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(req) {
(data, response, error) in
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
if let bookDictionary: AnyObject = jsonObject!["\(self.lookUpID)"] {
// Retrieve the author name
var names = [String]()
if let authors = bookDictionary["authors"] as? NSArray {
for author in authors {
if let author = author as? NSDictionary,
let name = author["name"] as? String {
names.append(name)
}
}
}
// Retrieve cover url
var coverThumbURL: String = ""
if let thumbs = bookDictionary["cover"] as? NSArray {
// This code isn't running at all.
for thumb in thumbs {
if let thumb = thumb as? NSDictionary,
let thumbnail = thumb["medium"] as? String {
coverThumbURL = thumbnail
}
}
}
}
Thanks for the help. I did some looking around & fixed the casting.
var coverThumbURL: String = ""
if let thumbs = bookDictionary["cover"] as? NSDictionary {
let thumbnail = thumbs.valueForKey("medium") as? String
coverThumbURL = thumbnail!
}