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;