I'm making an api call to randomuser.me and getting back a json file containing (amongst other data):
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
}
I'd like to display the date string in a text label as "dd-MMM-yyyy" but can't figure out how to format the string to achieve this.
I'v tried converting it into a date using ISO8601DateFormatter and then back into a string but have had no luck so far.
Can anyone help?
func getUserData() {
let config = URLSessionConfiguration.default
config.urlCache = URLCache.shared
let session = URLSession(configuration: config)
let url = URL(string: "https://randomuser.me/api/?page=\(page)&results=20&seed=abc")!
let urlRequest = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 15.0)
let task = session.dataTask(with: urlRequest) { data, response, error in
// Check for errors
guard error == nil else {
print ("error: \(error!)")
return
}
// Check that data has been returned
guard let content = data else {
print("No data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let fetchedData = try decoder.decode(User.self, from: content)
for entry in fetchedData.results {
self.usersData.append(entry)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let err {
print("Err", err)
}
}
// Execute the HTTP request
task.resume()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "userInfoCell") as! UserInfoCell
let user: Result
if isFiltering {
user = filteredData[indexPath.row]
} else {
user = usersData[indexPath.row]
}
cell.nameLabel.text = "\(user.name.first) \(user.name.last)"
cell.dateOfBirthLabel.text = user.dob.date
cell.genderLabel.text = user.gender.rawValue
cell.thumbnailImage.loadImageFromURL(user.picture.thumbnail)
return cell
}
import Foundation
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
let theDate = dateFormatter.date(from: "1993-07-20T09:44:18.674Z")!
let newDateFormater = DateFormatter()
newDateFormater.dateFormat = "dd-MMM-yyyy"
print(newDateFormater.string(from: theDate))
First convert the string to date using proper date format. Then convert it back to string using the format you want.
Generally, if manually converting 1993-07-20T09:44:18.674Z to a Date, we’d use ISO8601DateFormatter:
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
In this approach, it takes care of time zones and locales for us.
That having been said, if you’re using JSONDecoder (and its dateDecodingStrategy outlined below), then we should define our model objects to use Date types, not String for all the dates. Then we tell the JSONDecoder to decode our Date types for us, using a particular dateDecodingStrategy.
But that can’t use the ISO8601DateFormatter. We have to use a DateFormatter with a dateFormat of "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" and a locale of Locale(identifier: "en_US_POSIX"):
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0) // this line only needed if you ever use the same formatter to convert `Date` objects back to strings, e.g. in `dateEncodingStrategy` of `JSONEncoder`
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(formatter)
See the “Working With Fixed Format Date Representations” sections of the DateFormatter documentation.
Then, for your formatter for your UI, if you absolutely want dd-MMM-yyyy format, you would have a separate formatter for that, e.g.:
let formatter = DateFormatter()
formatter.dateFormat = "dd-MMM-yyyy"
Note, for this UI date formatter, we don’t set a locale or a timeZone, but rather let it use the device’s current defaults.
That having been said, we generally like to avoid using dateFormat for date strings presented in the UI. We generally prefer dateStyle, which shows the date in a format preferred by the user:
let formatter = DateFormatter()
formatter.dateStyle = .medium
This way, the US user will see “Nov 22, 2019”, the UK user will see “22 Nov 2019”, and the French user will see “22 nov. 2019”. Users will see dates in the formats with which they are most accustomed.
See “Working With User-Visible Representations of Dates and Times” in the aforementioned DateFormatter documentation.
pass your date into this
let dateFormatterPrint = DateFormatter()
dateFormatterPrint.dateFormat = "dd-MM-yyyy"
let val = dateFormatterPrint.string(from: "pass your date")
You can use extensions for you to be able to convert the date to your desired format.
var todayDate = "1993-07-20T09:44:18.674Z"
extension String {
func convertDate(currentFormat: String, toFormat : String) -> String {
let dateFormator = DateFormatter()
dateFormator.dateFormat = currentFormat
let resultDate = dateFormator.date(from: self)
dateFormator.dateFormat = toFormat
return dateFormator.string(from: resultDate!)
}
}
Then you can implement like this:
cell.dateOfBirthLabel.text = self.todayDate.convertDate(currentFormat: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", toFormat: "dd-MMM-yyyy")
Related
Was only able to reproduce this issue on a friend's device. The device is from Germany and is set to the German region in Settings. I cannot reproduce on any Canadian devices. Why is it failing when trying to create a Date property from the JSON?
Console:
dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "60", intValue: nil), CodingKeys(stringValue: "expiration", intValue: nil)], debugDescription: "Date string does not match format expected by formatter.", underlyingError: nil))
Struct:
struct TokenResponse: Decodable {
var ticket : String
var expiration : Date?
var sessionId: String
}
Inside URLSession:
do {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(formatter)
let json = try decoder.decode([String: TokenResponse].self, from: data)
}
catch {
print(error)
}
JSON:
{
"60":{
"ticket":"aVeryLongJWT",
"expiration":"2022-02-04T22:00:34.8325102Z",
"sessionId":"aUUID"
}
}
You should set the locale before setting dateFormat string:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
See “Working With Fixed Format Date Representations” in DateFormatter documentation.
Previous answer is worked for me! You should check the date format coming from server. Mine was different and its case sensitive. I just arranged df.dateFormat Good luck!
private let dateFormatter: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss-hh:ss"
return df
}()
and then in the dataFetch function
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
let results = try! decoder.decode(Model.self, from: data)
print(results)
} catch {
print("Decoding Error!")
}
I have a JSON (from a third party) that I need to parse. This JSON returns several nested objects
articles: {
authors: {
birthday: 'DD-MM-YYYY'
}
relevant_until: 'YYYY-MM-DD HH:MM:SS'
publication_date: secondsSince1970,
last_comment: iso8601
}
I'm following this answer to have multiple date formatters and it works, as long as every date extracted from JSON is a string.
But when it comes to the secondsSince1970 (UNIX epoc time) I can't find a way to parse it as a codable object. Everywhere I see the Date(timeIntervalSince1970: timestamp) and I don't know how to use it when decoding it
How do I parse the dates on this object when a date can be passed as a TimeInterval or as a String?
try jsonDecoder.decode(Articles.self, from: jsonData)
extension Formatter {
static let iso8601withFractionalSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
return formatter
}()
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter
}()
static let ddMMyyyy: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "dd-MM-yyyy"
return formatter
}()
}
extension JSONDecoder.DateDecodingStrategy {
static let multiple = custom {
let container = try $0.singleValueContainer()
do {
return try Date(timeIntervalSince1970: container.decode(Double.self))
} catch DecodingError.typeMismatch {
let string = try container.decode(String.self)
if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ??
Formatter.iso8601.date(from: string) ??
Formatter.ddMMyyyy.date(from: string) {
return date
}
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
}
}
}
Playground testing:
struct Root: Codable {
let articles: Articles
}
struct Articles: Codable {
let authors: Authors
let relevantUntil: Date
let publicationDate: Date
let lastComment: Date
}
struct Authors: Codable {
let birthday: Date
}
let json = """
{"articles": {
"authors": {"birthday": "01-01-1970"},
"relevant_until": "2020-11-19 01:23:45",
"publication_date": 1605705003.0019,
"last_comment": "2020-11-19 01:23:45.678"}
}
"""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .multiple
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(Root.self, from: .init(json.utf8))
print(root.articles) // Articles(authors: __lldb_expr_107.Authors(birthday: 1970-01-01 03:00:00 +0000), relevantUntil: 2020-11-19 04:23:45 +0000, publicationDate: 2020-11-18 13:10:03 +0000, lastComment: 2020-11-19 04:23:45 +0000)
} catch {
print(error)
}
Following the same logic you can try to decode the JSON property as TimeInterval (or Double) and if that fails, fall back to your String handling:
extension JSONDecoder {
var dateDecodingStrategyFormatters: [DateFormatter]? {
#available(*, unavailable, message: "This variable is meant to be set only")
get { return nil }
set {
guard let formatters = newValue else { return }
self.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
do {
let timeInterval = try container.decode(TimeInterval.self)
return Date(timeIntervalSince1970: timeInterval)
} catch DecodingError.typeMismatch {
let dateString = try container.decode(String.self)
for formatter in formatters {
if let date = formatter.date(from: dateString) {
return date
}
}
}
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date")
}
}
}
}
This question already has answers here:
How to convert a date string with optional fractional seconds using Codable in Swift?
(4 answers)
Closed 3 years ago.
Suppose we have the following structure:
struct TestCod: Codable {
var txt = ""
var date = Date()
}
If we are expecting JSON in two similar formats, how could we handle decoding?
JSON A:
{
"txt":"stack",
"date":589331953.61679399
}
JSON B:
{
"txt":"overflow",
"date":"2019-09-05"
}
As shown in the possible duplicate post I have already mentioned in comments, you need to create a custom date decoding strategy:
First create your date formatter for parsing the date string (note that this assumes your date is local time, if you need UTC time or server time you need to set the formatter timezone property accordingly):
extension Formatter {
static let yyyyMMdd: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
}
Then create a custom decoding strategy to try all possible date decoding strategy you might need:
extension JSONDecoder.DateDecodingStrategy {
static let deferredORyyyyMMdd = custom {
let container = try $0.singleValueContainer()
do {
return try Date(timeIntervalSinceReferenceDate: container.decode(Double.self))
} catch {
let string = try container.decode(String.self)
if let date = Formatter.yyyyMMdd.date(from: string) {
return date
}
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
}
}
}
Playground testing:
struct TestCod: Codable {
let txt: String
let date: Date
}
let jsonA = """
{
"txt":"stack",
"date":589331953.61679399
}
"""
let jsonB = """
{
"txt":"overflow",
"date":"2019-09-05"
}
"""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .deferredORyyyyMMdd
let decodedJSONA = try! decoder.decode(TestCod.self, from: Data(jsonA.utf8))
decodedJSONA.date // "Sep 4, 2019 at 8:19 PM"
let decodedJSONB = try! decoder.decode(TestCod.self, from: Data(jsonB.utf8))
decodedJSONB.date // "Sep 5, 2019 at 12:00 AM"
Need to encode into JSON, a struct that has 2 Date instance variables (day and time), however, I need to encode each date instance variable with a different format, ie. for "day":"yyyy-M-d" and "time":"H:m:s".
Have written a custom decoder which works no problems. But not sure how to write the required custom encoder to solve this.
For example I can decode the following JSON string:
{ "biometrics" : [
{"biometricId":1,"amount":2.1,"source":"Alderaan","day":"2019-1-3","time":"11-3-3","unitId":2},
{"biometricId":10,"amount":3.1,"source":"Endoor","day":"2019-2-4","time":"11-4-4","unitId":20}]
}
However, when I encode it, I can only encode it in a single date format :(
Help, would be greatly appreciated.
Thank you.
import UIKit
let biometricsJson = """
{ "biometrics" : [
{"biometricId":1,"amount":2.1,"source":"Alderaan","day":"2019-1-3","time":"11-3-3","unitId":2},
{"biometricId":10,"amount":3.1,"source":"Endoor","day":"2019-2-4","time":"11-4-4","unitId":20}]
}
"""
struct Biometrics: Codable {
var biometrics: [Biometric]
}
struct Biometric: Codable {
var biometricId: Int
var unitId: Int
var source: String?
var amount: Double
var day: Date
var time: Date
init(biometricId: Int, unitId: Int, source: String, amount: Double, day: Date, time: Date){
self.biometricId = biometricId
self.unitId = unitId
self.source = source
self.amount = amount
self.day = day
self.time = time
}
}
extension Biometric {
static let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "H:m:s"
if let date = formatter.date(from: dateString) {
return date
}
formatter.dateFormat = "yyyy-M-d"
if let date = formatter.date(from: dateString) {
return date
}
throw DecodingError.dataCorruptedError(in: container,
debugDescription: "Cannot decode date string \(dateString)")
}
return decoder
}()
}
let biometrics = try Biometric.decoder.decode(Biometrics.self, from:biometricsJson.data(using: .utf8)!)
let jsonEncoder = JSONEncoder()
let encodedJson = try jsonEncoder.encode(biometrics)
let jsonString = String(data: encodedJson, encoding: .utf8)
if biometricsJson != jsonString {
print("error: decoding, then encoding does not give the same string")
print("biometricsJson: \(biometricsJson)")
print("jsonString: \(jsonString!)")
}
I expect the encoded JSON, to be decodable by the decoder.
i.e. biometricsJson == jsonString
In a custom encode(to:), just encode each one as a string using the desired formatter. There's no "date" type in JSON; it's just a string. Something along these lines:
enum CodingKeys: CodingKey {
case biometricId, amount, source, day, time, unitId
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(biometricId, forKey: .biometricId)
try container.encode(unitId, forKey: .unitId)
try container.encode(source, forKey: .source)
try container.encode(amount, forKey: .amount)
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "H:m:s"
let timeString = formatter.string(from: time)
try container.encode(timeString, forKey: .time)
formatter.dateFormat = "yyyy-M-d"
let dayString = formatter.string(from: day)
try container.encode(dayString, forKey: .day)
}
But note that you can't test for equivalent strings. JSON dictionaries aren't order-preserving, so there's no way to guarantee a character-by-character match.
Note that if you really want to have days and times, you should consider DateComponents rather than a Date. A date is a specific instance in time; it's not in any time zone, and it can't be just an hour, minute, and second.
Also, your use of Double is going to cause rounding differences. So 2.1 will be encoded as 2.1000000000000001. If that's a problem, you should use Decimal for amount rather than Double.
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.