For loop does not go beyond else { continue } - json

I am trying to use JSON data for the first time and I am really struggling with it. I want to show the data in a UIView with some labels but when I add some breakpoints after the line else { continue } the step thru process goes right past else { continue } and continues on past all of my code after that point and so the employee is never added to my object and I can't ever confirm if my code even works by printing it with the print() method. I also am encountering the following error: Cannot convert value of type '[String : Any]' to expected argument type '[String]?'
I'm not really sure what I could do to fix this and any advice would be greatly appreciated.
Here is my ViewController.swift file's code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var empName: UILabel!
#IBOutlet weak var empUsername: UILabel!
#IBOutlet weak var empSkills: UILabel!
#IBOutlet weak var pastEmployers: UILabel!
#IBOutlet weak var textFieldNote: UITextField!
var employees = [Employee]() // An empty array of employees
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Get the path to the EmployeeData.json file
if let path = Bundle.main.path(forResource: "EmployeeData", ofType: ".json" ) {
let url = URL(fileURLWithPath: path)
do {
// Create a data object from the files url
// After this line executes, our file is in binary format inside the "data" constant
let data = try Data.init(contentsOf: url)
// Create a json Object from the binary Data file.
// Catch it as an array of Any type objects
let jsonObj = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [Any]
// At this point we have an array of Any objects that represents our JSON data from our file
// We can now parse through the jsonObj and start instantiating our Employee objects.
Parse(jsonObject: jsonObj)
}
catch {
print(error)
}
}
}
func Parse(jsonObject: [Any]?) {
guard let json = jsonObject
else {print("Parse failed to unwrap the optional."); return }
// Looping through the first level objects
for firstLevelItems in json {
// Try to convert first level object into a [String: Any]
guard let object = firstLevelItems as? [String: Any],
let ename = object["employeename"] as? String,
let eusername = object["username"] as? String,
let emacaddress = object["macaddress"] as? String,
let ecurrentTitle = object["current_title"] as? String,
let eskills = object["skills"] as? [String],
let epastEmployers = object["past_employers"] as? [String: Any],
let pcompany = epastEmployers["company"] as? String,
let presponsibilities = epastEmployers["responsibilities"] as? String,
let ecompany = object["company"] as? String,
let eresponsibilities = object["responsibilities"] as? [String]
// if anything in the guard fails, immediately continue to the next iteration of the loop
else { continue }
employees.append(Employee(name: ename, username: eusername, macaddress: emacaddress, currentTitle: ecurrentTitle, skills: eskills, pastEmployers: epastEmployers, currentCompany: ecompany, responsibilities: eresponsibilities))
// Build Object here
//Check to see if this current employee exists yet
// Filter is a fast and elegant way to make this comparison
// Looping through each object would be tedious and slow
//
// var tempString = "\(ename) \(eusername) \(emacaddress) \(ecurrentTitle) \(eskills) \(ecompany) \(eresponsibilities)"
//
// if let pastEmployer = epastEmployers["company"] as? String {
//
// tempString += "\(pastEmployer)"
// }
// else if let currentResponsibilities = eresponsibilities["responsibilities"] as? String {
//
// tempString += "\(currentResponsibilities)"
// }
}
}
// func addEmployee(_employee: Employee) {
// if let currentSkills = eskills["skills"] as? String {
//
// _employee.skills.append()
// }
// }
func displayEmployee() {
empName.text = employees[0].name.description
}
#IBAction func currentEmpChanged(_ sender: UIButton) {
}
}
And here is my Employee.swift file's code:
//
// Employee.swift
// BoschBrookeCE02
//
// Created by Brooke Bosch on 7/3/21.
//
import Foundation
class Employee {
// Stored properties
let name: String
let username: String
let macaddress: String
let currentTitle: String
let skills: [String]
let pastEmployers: [String]!
let currentCompany: String
let responsibilities: [String]
// computed properties
// Initializers
init(name: String, username: String, macaddress: String, currentTitle: String, skills: [String], pastEmployers: [String]!, currentCompany: String, responsibilities: [String]) {
self.name = name
self.username = username
self.macaddress = macaddress
self.currentTitle = currentTitle
self.skills = skills
self.pastEmployers = pastEmployers
self.currentCompany = currentCompany
self.responsibilities = responsibilities
}
// Methods
}
And finally, my EmployeeData.json file
[{"employeename":"Shawn Cunningham","username":"scunningham0","macaddress":"41-6A-75-B0-2E-73","current_title":"Programmer Analyst IV","skills":["VPython","EES","CD covers","RED MX"],"past_employers":[{"company":"Zboncak-O'Conner","responsibilities":["Extended next generation database"]},{"company":"Friesen, Runolfsson and Kautzer","responsibilities":["Switchable discrete conglomeration","Synergized client-driven project","Universal full-range website","Secured optimal productivity","Balanced multimedia solution"]},{"company":"Zieme-Cronin","responsibilities":["Cloned empowering solution"]}]},
{"employeename":"Martin Wallace","username":"mwallace1","macaddress":"AE-B2-85-08-74-8F","current_title":"Associate Professor","skills":["FBT","Theory","CMMI Level 5","MQL"],"past_employers":[]},
{"employeename":"Roy Young","username":"ryoung2","macaddress":"7C-BD-D4-15-65-ED","current_title":"Mechanical Systems Engineer","skills":["EPA"],"past_employers":[{"company":"Hauck Group","responsibilities":[]},{"company":"DuBuque, Wyman and Kuvalis","responsibilities":[]},{"company":"Hickle and Sons","responsibilities":["Visionary zero defect customer loyalty","Decentralized responsive implementation","Inverse user-facing data-warehouse"]}]},
{"employeename":"Anthony Berry","username":"aberry3","macaddress":"AE-EE-3F-5E-2F-07","current_title":"Help Desk Operator","skills":["KOL Identification","Web Content","JDeveloper","DB2"],"past_employers":[{"company":"Champlin-Emard","responsibilities":["Cross-platform 3rd generation service-desk","Synergized 6th generation paradigm"]}]},
{"employeename":"Jerry Murphy","username":"jmurphy4","macaddress":"0F-BA-ED-D5-95-D5","current_title":"Sales Representative","skills":["Ehcache","FNMA"],"past_employers":[{"company":"Heidenreich Inc","responsibilities":["Digitized encompassing service-desk","Sharable transitional instruction set","Re-engineered tertiary flexibility","Multi-lateral composite moratorium","Multi-tiered methodical orchestration"]}]},
{"employeename":"Gregory Lawrence","username":"glawrence5","macaddress":"E5-BC-80-A4-A4-3E","current_title":"Financial Analyst","skills":["Gutters","Karl Fisher"],"past_employers":[{"company":"Buckridge-Armstrong","responsibilities":["Robust 5th generation core"]},{"company":"Medhurst-Kovacek","responsibilities":["Operative disintermediate middleware","Optional dedicated installation","Multi-tiered foreground artificial intelligence","Automated attitude-oriented algorithm"]},{"company":"Kuhic, Beer and Runolfsson","responsibilities":["Assimilated explicit adapter","Ameliorated maximized benchmark"]}]},
{"employeename":"Linda Myers","username":"lmyers6","macaddress":"BF-9D-78-1C-8E-E1","current_title":"Financial Advisor","skills":["Special Effects","PCI DSS"],"past_employers":[{"company":"Weber-Ernser","responsibilities":[]},{"company":"Homenick, Nicolas and Kassulke","responsibilities":["Right-sized attitude-oriented instruction set","Customizable scalable help-desk","Secured mission-critical intranet","Versatile stable help-desk","Synergized composite initiative"]},{"company":"Zemlak Inc","responsibilities":["Seamless upward-trending circuit","Advanced object-oriented software","Business-focused asymmetric project"]}]},
{"employeename":"Alice Hunter","username":"ahunter7","macaddress":"A9-65-3D-10-01-8C","current_title":"Nurse","skills":["Estate Jewelry","Legal Research","Social Media Blogging"],"past_employers":[{"company":"Abernathy Group","responsibilities":["Business-focused empowering toolset","Object-based systemic application","Triple-buffered demand-driven analyzer","Cross-platform didactic intranet"]}]},
{"employeename":"Lois Elliott","username":"lelliott8","macaddress":"03-93-D1-52-7D-91","current_title":"Research Assistant IV","skills":["DDR2","BCM","Airline Management","IT Infrastructure Management","Helicopters"],"past_employers":[{"company":"Lesch-Kunze","responsibilities":["Ergonomic analyzing contingency","Triple-buffered radical moratorium","Realigned zero tolerance functionalities","Vision-oriented mission-critical access"]}]},
{"employeename":"Sandra Alvarez","username":"salvarez9","macaddress":"C4-BF-22-9C-02-44","current_title":"Paralegal","skills":["DSE Assessments"],"past_employers":[{"company":"Murphy-Zemlak","responsibilities":["Optional transitional intranet","Managed bi-directional functionalities","Synergized uniform toolset"]},{"company":"Howe LLC","responsibilities":["Self-enabling content-based intranet","Enterprise-wide tertiary project"]},{"company":"Dare and Sons","responsibilities":["Decentralized upward-trending capacity","Operative discrete ability","Right-sized bottom-line orchestration","User-centric exuding conglomeration"]}]},
{"employeename":"Diane Russell","username":"drussella","macaddress":"5C-2D-32-7D-01-7F","current_title":"Registered Nurse","skills":["KnockoutJS","Wufoo","IAS 39","Childhood Obesity","Amazon RDS"],"past_employers":[]},
{"employeename":"Amanda Grant","username":"agrantb","macaddress":"FC-B6-7B-B3-77-47","current_title":"Software Test Engineer IV","skills":["AVEVA PDMS"],"past_employers":[{"company":"Miller Group","responsibilities":["Assimilated hybrid migration","Future-proofed regional capacity","Re-contextualized system-worthy migration"]},{"company":"Smitham-Kunze","responsibilities":["Programmable 24/7 firmware","Networked systematic throughput","Streamlined interactive orchestration","Enterprise-wide 24/7 circuit","Organized didactic Graphic Interface"]},{"company":"Harvey Group","responsibilities":["Triple-buffered hybrid strategy","Switchable intangible hierarchy","Synergistic coherent Graphic Interface"]}]},
{"employeename":"Philip Stewart","username":"pstewartc","macaddress":"02-22-FB-BA-F9-DE","current_title":"Paralegal","skills":["WiMAX","Tubing"],"past_employers":[{"company":"Abernathy, Murazik and Gerhold","responsibilities":["Future-proofed object-oriented secured line","Distributed didactic portal","Reactive coherent challenge","Proactive transitional success"]},{"company":"Kassulke and Sons","responsibilities":["Programmable web-enabled open system","Grass-roots bi-directional encryption","Streamlined neutral projection","Diverse client-server protocol"]},{"company":"Wiegand, Powlowski and Flatley","responsibilities":["Operative disintermediate forecast"]}]},
{"employeename":"Jack Johnston","username":"jjohnstond","macaddress":"C4-0E-4D-8C-DE-7D","current_title":"Nurse Practicioner","skills":["BDD","Risk Assessment","KPI Reports"],"past_employers":[]},
{"employeename":"Ralph Bishop","username":"rbishope","macaddress":"D5-0D-1B-D8-97-E2","current_title":"Programmer IV","skills":[],"past_employers":[]},
{"employeename":"Martin Smith","username":"msmithf","macaddress":"B3-A1-20-AC-FA-ED","current_title":"Automation Specialist IV","skills":["QNX","Integrated Circuit Design"],"past_employers":[{"company":"Denesik-Hamill","responsibilities":["Compatible leading edge pricing structure","Realigned maximized help-desk","User-friendly foreground productivity"]}]},
{"employeename":"Laura Lawson","username":"llawsong","macaddress":"21-73-52-CD-50-1C","current_title":"Senior Developer","skills":["International Sales","ODI","TCD"],"past_employers":[{"company":"Brekke-Schmeler","responsibilities":[]}]},
{"employeename":"Lisa Mills","username":"lmillsh","macaddress":"E8-31-0D-D1-C8-22","current_title":"Technical Writer","skills":["Joomla","GNU C","PWS","Aerial Lifts"],"past_employers":[]},
{"employeename":"Louis Perez","username":"lperezi","macaddress":"42-35-3F-2A-A9-52","current_title":"Environmental Specialist","skills":["RPM","Nutritional Counseling","Zendesk"],"past_employers":[{"company":"Rutherford, Okuneva and Steuber","responsibilities":["Devolved attitude-oriented intranet","Stand-alone disintermediate budgetary management","Decentralized value-added function","Multi-channelled dedicated intranet"]},{"company":"DuBuque-Schroeder","responsibilities":["Automated stable circuit","Virtual coherent groupware"]},{"company":"Jast, Rippin and Nicolas","responsibilities":["Multi-layered intangible moratorium","Right-sized web-enabled emulation","Business-focused uniform capacity","Organized eco-centric toolset","Seamless solution-oriented moderator"]}]},
{"employeename":"Bonnie Snyder","username":"bsnyderj","macaddress":"5F-A8-ED-1E-DF-AF","current_title":"Account Coordinator","skills":[],"past_employers":[{"company":"Champlin, Ritchie and Wiegand","responsibilities":["Persevering fresh-thinking focus group","Visionary stable core"]}]}]

Your past_employers is an Array<Dictionary>, NOT a Dictionary.
"past_employers":[
{"company":"Zboncak-O'Conner","responsibilities":["Extended next generation database"]},
{"company":"Friesen, Runolfsson and Kautzer","responsibilities":["Switchable discrete conglomeration","Synergized client-driven project","Universal full-range website","Secured optimal productivity","Balanced multimedia solution"]},
{"company":"Zieme-Cronin","responsibilities":["Cloned empowering solution"]}
]
This line is failing.
let epastEmployers = object["past_employers"] as? [String: Any]
If you change it to
let epastEmployers = object["past_employers"] as? [[String: Any]]
It will now force you to access all the employer information from the Array NOT Dictionary. So all of the following lines would have to be inside a for loop.
// Wrong, `epastEmployers` is an Array
let pcompany = epastEmployers["company"] as? String,
let presponsibilities = epastEmployers["responsibilities"] as? String,
let ecompany = object["company"] as? String,
let eresponsibilities = object["responsibilities"] as? [String]
// Right
for epastEmployer in epastEmployers {
let ecompany = epastEmployer["company"] as? String
let eresponsibilities = epastEmployer["responsibilities"] as? [String]
}

Related

SIGABRT error in Swift when JSON deserializing

I am attempting to make a Swift application with two main focuses. One is to display all the data from an URL in a ScrollView and the other is a button to get a random name of a game.
The button works and I get a random game but when I try to load the application with the UIScrollView, I get a SIGABRT on line 33.
Any help is appreciated
EDIT: I have since fixed the SIGABRT but I can't seem to display any information into the UIScrollView. Anyone see any glaring issues in the code now?
#IBOutlet weak var infoView: UIView!
#IBOutlet weak var label: UILabel!
#IBOutlet weak var labelScroll: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
parseGame()
}
func parseGame() {
let url: URL = URL(string: "https://www.giantbomb.com/api/games/?api_key=5094689370c2cf4ae42a2a268af0595badb1fea8&format=json&field_list=name")!
print(url)
let responseData: Data? = try? Data(contentsOf: url)
if let responseData = responseData {
let json: Any? = try? JSONSerialization.jsonObject(with: responseData, options: [])
print(json ?? "Couldn't get JSON")
if let json = json {
let dictionary: [String: Any]? = json as? [String: Any]
if let dictionary = dictionary {
guard let result = dictionary["results"] as? [String:Any]? else { return }
if let result = result {
let name = result["name"] as? String?
if let name = name {
for i in 1...100 {
labelScroll.text = "Name: \(name)"
}
}
}
}
}
}
}
Oh no! It's the json parsing pyramid of death!
A Neat Little Alternative
If you know the structure of the json you will be receiving, you can create some structs to model your data. Also Swift has a cool protocol called "Codable" which does a lot of heavy lifting in parsing json and converting it to an object you create in code.
Let's Model the Data!
We'll create 2 structs that conform to the Codable Protocol. The first will hold the entire response data
struct ApiGameReponse:Codable {
var error:String
var limit:Int
var offset:Int
var pages:Int
var totalResults:Int
var status:Int
var version:String
var games:[Game]
private enum CodingKeys:String, CodingKey {
//The enum's rawValue needs to match the json's fieldName exactly.
//That's why some of these have a different string assigned to them.
case error
case limit
case offset
case pages = "number_of_page_results"
case totalResults = "number_of_total_results"
case status = "status_code"
case version
case games = "results"
}
}
and the second struct to model our game object. (Which is a single string, yes I know very exciting...) Since this particular json data represents a game with only 1 name property we don't really need a whole struct, but if the json was different and say had a "gamePrice" and "genre" properties, a struct would be nicer to look at.
struct Game:Codable {
var name:String
}
Keepin it pretty
Idk about you, but I don't like to look at ugly code. To keep it clean, we're going to split the function you made into two pieces. It's always better to have a bunch of smaller readable/reusable functions that each complete a single task rather than 1 superSizeMe number 3 with a large coke.
Getting the Data
Piece 1: Get the data from the URL
func getJSONData(_ urlString: String) -> Data? {
guard
let url:URL = URL(string: urlString),
let jsonData:Data = try? Data(contentsOf: url)
else { return nil }
return jsonData
}
Piece 2: Decode the data into something we can use
func getGameNames() -> [String] {
let apiEndpoint = "https://www.giantbomb.com/api/games/?api_key=5094689370c2cf4ae42a2a268af0595badb1fea8&format=json&field_list=name"
guard
let data = getJSONData(apiEndpoint),
let response = try? JSONDecoder().decode(ApiGameReponse.self, from: data)
else { return [] }
//Neat little function "map" allows us to create an array names from the array of game objects.
let nameArray = response.games.map { $0.name }
return nameArray
}
Implementation
And finally we can get the names and use them as needed.
override func viewDidLoad() {
//I recommend putting this somewhere else
//Perhaps create a method called "setup()" and call it in this class's inializer.
let names = getGameNames()
print(names) //Implement the array of names as needed
}

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.

Result of initializer unused

I was just wondering why I get this error. I have struct as 'Model' and initialize it from the extract_data function, which is called in viewDidLoad(), but when I set a breakpoint in the struct I can see that everything was initialised properly and whats strange to is if i take my label and set it to my Struct.base String, it doesn't work somehow...I'm a beginner so an explanation would really be appreciated:)
Here is my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
get_data("https://api.fixer.io/latest?base=CHF")
testLabel.text = TestStruct().base
//ANOTHER ERROR HERE (MISSING ARGUMENT FOR PARAMETER "base" IN CALL)
}
#IBOutlet var testLabel: UILabel!
var test:[TestStruct] = [TestStruct]()
func get_data(_ link:String){
let url:URL = URL(string: link)!
let sessioin = URLSession.shared
let request = URLRequest(url: url)
let testtask = sessioin.dataTask(with: request, completionHandler: { (data, response, error) in
self.extract_data(data)
})
testtask.resume()
}
func extract_data(_ data:Data?){
let json:Any?
if(data==nil){
return
}
do {
json = try JSONSerialization.jsonObject(with: data!, options: [])
//The whole JSON
let data_dictonary = json as? [String: Any]
//Just the "base"
let data_base = data_dictonary?["base"] as! String
print(data_base)
//Just the "date"
let data_date = data_dictonary?["date"] as! String
print(data_date)
//All the exchange rates
let data_rates = data_dictonary?["rates"] as! [String: Double]
print(data_rates)
TestStruct.init(base: data_base, date: data_date, rates: data_rates)
//ERROR IS HERE
}catch {
return
}
}
Here is my Model:
struct TestStruct {
var base: String
var date: String
var rates: [String: Double]
init(base: String, date: String, rates: [String:Double]) {
self.base = base
self.date = date
self.rates = rates
}
}
Your syntax for calling the initializer is incorrect. Change:
TestStruct.init(base: data_base, date: data_date, rates: data_rates)
to:
let someVar = TestStruct(base: data_base, date: data_date, rates: data_rates)
And the error with:
testLabel.text = TestStruct().base
is that you are trying to access the base property on a newly created TestStruct instance but you have no init on your TestStruct with no parameters.
You need to create TestStruct by passing all of the required arguments but that seems pointless since all you want is its base and you need the base to create the instance to begin with.

Swift unable to pull information from other Swift files unless in #IBAction

I'm currently working with the latest version of Swift. In a nutshell, I'm pulling information from a webpage and storing said information into an array. Here is how I did that (forgive the indenting..):
class TransactionData {
var transactions: [Transaction] = []
init() {
getTransactionData()
}
func getTransactionData() {
let jsonUrl = "php file with json"
let session = NSURLSession.sharedSession()
let shotsUrl = NSURL(string: jsonUrl)
let task = session.dataTaskWithURL(shotsUrl!) {
(data, response, error) -> Void in
do {
let jsonData: NSArray = (try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSArray)!
for var index = 0; index < jsonData.count; ++index {
let orderID: String = jsonData[index]["orderID"] as! String
let orderDate: String = jsonData[index]["orderDate"] as! String
let orderType: String = jsonData[index]["orderType"] as! String
let paymentType: String = jsonData[index]["paymentType"] as! String
let itemName: String = jsonData[index]["itemName"] as! String
let itemPrice: String = jsonData[index]["itemPrice"] as! String
let itemTaxes: String = jsonData[index]["itemTaxes"] as! String
let orderModifications: String = jsonData[index]["orderModifications"] as! String
let orderVariations: String = jsonData[index]["orderVariations"] as! String
let transaction = Transaction(orderID: orderID, orderDate: orderDate, orderType: orderType, paymentType: paymentType, itemName: itemName, itemPrice: itemPrice, itemTaxes: itemTaxes, orderModifications: orderModifications, orderVariations: orderVariations)
self.transactions.append(transaction)
}
} catch _ {
// Error
}
}
task.resume()
}
When I want to call the information, I use this:
let transactionData = TransactionData()
for transaction in transactionData.transactions {
print("\(transaction)")
}
The only time information gets pulled through to a ViewController is when I'm using an IBAction. If I try anywhere else, it doesn't read the information through. For example, I'm trying to pull information from the online website to pass into a TableViewController. It just won't pull the information.
Any ideas?
You need to establish a connection between your ViewControllers so they can pass data between them. One common way is through a delegate protocol that is set during a segue between screens. Here is a tutorial on it.
To expand on this a little bit, it sounds like you have a class Transactions that is called by your ViewController to load data. If you then try to access a new instance of that in your TableViewController, you don't have the data, because a new instance is being created. There are two ways to avoid this issue:
Pass data (or a reference to your Transactions) from VC -> TVC via delegate.
Use a singleton pattern for your data model so that all can access it.
To avoid concurrency issue, I would suggest doing the former.

Best way to convert JSON or other untyped data into typed classes in Swift?

I'm trying to parse out JSON into typed classes for safety/convenience, but it's proving very clunky. I wasn't able to find a library or even a post for Swift (Jastor is as close as I got). Here's a fabricated little snippet to illustrate:
// From NSJSONSerialization or similar and casted to an appropriate toplevel type (e.g. Dictionary).
var parsedJson: Dictionary<String, AnyObject> = [ "int" : 1, "nested" : [ "bool" : true ] ]
class TypedObject {
let stringValueWithDefault: String = ""
let intValueRequired: Int
let nestedBoolBroughtToTopLevel: Bool = false
let combinedIntRequired: Int
init(fromParsedJson json: NSDictionary) {
if let parsedStringValue = json["string"] as? String {
self.stringValueWithDefault = parsedStringValue
}
if let parsedIntValue = json["int"] as? Int {
self.intValueRequired = parsedIntValue
} else {
// Raise an exception...?
}
// Optional-chaining is actually pretty nice for this; it keeps the blocks from nesting absurdly.
if let parsedBool = json["nested"]?["bool"] as? Bool {
self.nestedBoolBroughtToTopLevel = parsedBool
}
if let parsedFirstInt = json["firstInt"] as? Int {
if let parsedSecondInt = json["secondInt"] as? Int {
self.combinedIntRequired = parsedFirstInt * parsedSecondInt
}
}
// Most succinct way to error if we weren't able to construct self.combinedIntRequired?
}
}
TypedObject(fromParsedJson: parsedJson)
There's a number of issues here that I'm hoping to work around:
It's extremely verbose, since I need to wrap every single property in a copy-pasted if-let for safety.
I'm not sure how to communicate errors when required properties are missing (as noted above). Swift seems to prefer (?) using exceptions for show-stopping problems (rather than pedestrian malformed data as here).
I don't know a nice way to deal with properties that exist but are the wrong type (given that the as? casting will fail and simply skip the block, it's not very informative to the user).
If I want to translate a few properties into a single one, I need to nest the let blocks proportional to the number of properties I'm combining. (This is probably more generally a problem with combining multiple optionals into one value safely).
In general, I'm writing imperative parsing logic when I feel like I ought to be able to do something a little more declarative (either with some stated JSON schema or at least inferring the schema from the class definition).
I do this using the Jastor framework:
1) Implement a Protocol that has a single function that returns an NSDictionary response:
protocol APIProtocol {
func didReceiveResponse(results: NSDictionary)
}
2) Create an API class that defines an NSURLConnection object that can be used as a Request URL for iOS's networking API. This class is created to simply return a payload from the itunes.apple.com API.
class API: NSObject {
var data: NSMutableData = NSMutableData()
var delegate: APIProtocol?
func searchItunesFor(searchTerm: String) {
// Clean up the search terms by replacing spaces with +
var itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+",
options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
var escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
var urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music"
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
println("Search iTunes API at URL \(url)")
connection.start()
}
// NSURLConnection Connection failed.
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
println("Failed with error:\(error.localizedDescription)")
}
// New request so we need to clear the data object.
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
self.data = NSMutableData()
}
// Append incoming data.
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
self.data.appendData(data)
}
// NSURLConnection delegate function.
func connectionDidFinishLoading(connection: NSURLConnection!) {
// Finished receiving data and convert it to a JSON object.
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
delegate?.didReceiveResponse(jsonResult)
}
}
3) Create a class with associated properties that inherits from Jastor
NSDictionary response:
{
"resultCount" : 50,
"results" : [
{
"collectionExplicitness" : "notExplicit",
"discCount" : 1,
"artworkUrl60" : "http:\/\/a4.mzstatic.com\/us\/r30\/Features\/2a\/b7\/da\/dj.kkirmfzh.60x60-50.jpg",
"collectionCensoredName" : "Changes in Latitudes, Changes in Attitudes (Ultmate Master Disk Gold CD Reissue)"
}
]
}
Music.swift
class Music : Jastor {
var resultCount: NSNumber = 0
}
4) Then in your ViewController be sure to set the delegate to self and then make a call to the API's searchITunesFor() method.
var api: API = API()
override func viewDidLoad() {
api.delegate = self;
api.searchItunesFor("Led Zeppelin")
}
5) Implement the Delegate method for didReceiveResponse(). Jastor extends your class to set a NSDictionary of the results returned from the iTunes API.
// #pragma - API Delegates
func didReceiveResponse(results: NSDictionary) {
let music = Music(dictionary: results)
println(music)
}
Short version: Since init isn't allowed to fail, validation has to happen outside of it. Optionals seem to be the intended tool for flow control in these cases. My solution is to use a factory method that returns an optional of the class, and use option chaining inside it to extract and validate the fields.
Note also that Int and Bool aren't children of AnyObject; data coming from an NSDictionary will have them stored as NSNumbers, which can't be cast directly to Swift types. Thus the calls to .integerValue and .boolValue.
Long version:
// Start with NSDictionary since that's what NSJSONSerialization will give us
var invalidJson: NSDictionary = [ "int" : 1, "nested" : [ "bool" : true ] ]
var validJson: NSDictionary = [
"int" : 1,
"nested" : [ "bool" : true ],
"firstInt" : 3,
"secondInt" : 5
]
class TypedObject {
let stringValueWithDefault: String = ""
let intValueRequired: Int
let nestedBoolBroughtToTopLevel: Bool = false
let combinedIntRequired: Int
init(intValue: Int, combinedInt: Int, stringValue: String?, nestedBool: Bool?) {
self.intValueRequired = intValue
self.combinedIntRequired = combinedInt
// Use Optionals for the non-required parameters so
// we know whether to leave the default values in place
if let s = stringValue {
self.stringValueWithDefault = s
}
if let n = nestedBool {
self.nestedBoolBroughtToTopLevel = n
}
}
class func createFromDictionary(json: Dictionary<String, AnyObject>) -> TypedObject? {
// Validate required fields
var intValue: Int
if let x = (json["int"]? as? NSNumber)?.integerValue {
intValue = x
} else {
return nil
}
var combinedInt: Int
let firstInt = (json["firstInt"]? as? NSNumber)?.integerValue
let secondInt = (json["secondInt"]? as? NSNumber)?.integerValue
switch (firstInt, secondInt) {
case (.Some(let first), .Some(let second)):
combinedInt = first * second
default:
return nil
}
// Extract optional fields
// For some reason the compiler didn't like casting from AnyObject to String directly
let stringValue = json["string"]? as? NSString as? String
let nestedBool = (json["nested"]?["bool"]? as? NSNumber)?.boolValue
return TypedObject(intValue: intValue, combinedInt: combinedInt, stringValue: stringValue, nestedBool: nestedBool)
}
class func createFromDictionary(json: NSDictionary) -> TypedObject? {
// Manually doing this cast since it works, and the only thing Apple's docs
// currently say about bridging Cocoa and Dictionaries is "Information forthcoming"
return TypedObject.createFromDictionary(json as Dictionary<String, AnyObject>)
}
}
TypedObject.createFromDictionary(invalidJson) // nil
TypedObject.createFromDictionary(validJson) // it works!
I've also done the following to convert to/from:
class Image {
var _id = String()
var title = String()
var subTitle = String()
var imageId = String()
func toDictionary(dict dictionary: NSDictionary) {
self._id = dictionary["_id"] as String
self.title = dictionary["title"] as String
self.subTitle = dictionary["subTitle"] as String
self.imageId = dictionary["imageId"] as String
}
func safeSet(d: NSMutableDictionary, k: String, v: String) {
if (v != nil) {
d[k] = v
}
}
func toDictionary() -> NSDictionary {
let jsonable = NSMutableDictionary()
self.safeSet(jsonable, k: "title", v: self.title);
self.safeSet(jsonable, k: "subTitle", v: self.subTitle);
self.safeSet(jsonable, k: "imageId", v: self.imageId);
return jsonable
}
}
Then I simply do the following:
// data (from service)
let responseArray = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSArray
self.objects = NSMutableArray()
for item: AnyObject in responseArray {
var image = Image()
image.toDictionary(dict: item as NSDictionary)
self.objects.addObject(image)
}
If you want to POST the data:
var image = Image()
image.title = "title"
image.subTitle = "subTitle"
image.imageId = "imageId"
let data = NSJSONSerialization.dataWithJSONObject(image.toDictionary(), options: .PrettyPrinted, error: nil) as NSData
// data (to service)
request.HTTPBody = data;