turning the json result into dynamic - json

i have a very complicated issue for a beginner. firstly I have this result from json
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
and when I use https://app.quicktype.io to create the object for me it gives this and that is right.
import Foundation
// MARK: - APIResult
struct APIResult {
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [String: Quote]
}
// MARK: - Quote
struct Quote {
let tryeur: Double
}
but I don't want my currencies hardcoded like this so if I choose from: USD to : EUR in my app I want to get the result under Quote as USDEUR. And I also know that if I change anything in this struct it won't work. So how will make those currency selections dynamic to make it work in different currencies. This is a currency converter app and I want to get these rates and reflect it on a chart in my app. Thank you.
Edit: I think I need to get used to using stack overflow properly. Sorry for any inconvenience . At last I could get the dates and rates written in the console. my question is now :
how can i get these results in the console passed into my charts x(dates) and y axis(rates) ?
["2022-12-22": 19.803011, "2022-12-18": 19.734066, "2022-12-23": 19.907873, "2022-12-21": 19.79505, "2022-12-24": 19.912121, "2022-12-17": 19.756527, "2022-12-16": 19.752446, "2022-12-25": 19.912121, "2022-12-19": 19.794356, "2022-12-20": 19.824031]
this is the func i get these
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -9, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
let result: () = currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
print(result)
}
and this is my previously created and hardcoded charts
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
lineChart.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 240)
lineChart.center = view.center
view.addSubview(lineChart)
var entries = [ChartDataEntry]()
for x in 0..<10 {
entries.append(ChartDataEntry(x: Double(x), y: Double(x)))
}
let set = LineChartDataSet(entries: entries)
set.colors = ChartColorTemplates.material()
let data = LineChartData(dataSet: set)
lineChart.data = data
}

Decodable is pretty versatile and highly customizable.
Write a custom initializer and map the quote dictionary to an array of Quote instances which contains the date and the quote. The key TRYEUR is irrelevant and will be ignored.
let jsonString = """
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
"""
struct APIResult: Decodable {
private enum CodingKeys: String, CodingKey {
case success, timeframe, startDate = "start_date", endDate = "end_date", source, quotes
}
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [Quote]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
success = try container.decode(Bool.self, forKey: .success)
timeframe = try container.decode(Bool.self, forKey: .timeframe)
startDate = try container.decode(String.self, forKey: .startDate)
endDate = try container.decode(String.self, forKey: .endDate)
source = try container.decode(String.self, forKey: .source)
let quoteData = try container.decode([String: [String:Double]].self, forKey: .quotes)
quotes = quoteData.compactMap({ (key, value) in
guard let quote = value.values.first else { return nil }
return Quote(date: key, quote: quote)
}).sorted{$0.date < $1.date}
}
}
struct Quote {
let date: String
let quote: Double
}
do {
let result = try JSONDecoder().decode(APIResult.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}

I changed my api provider now but this new one is not that much different than the previous one . this is the response I get inside browser
{
"success": true,
"timeseries": true,
"start_date": "2022-04-01",
"end_date": "2022-04-05",
"base": "USD",
"rates": {
"2022-04-01": {
"TRY": 14.686504
},
"2022-04-02": {
"TRY": 14.686504
},
"2022-04-03": {
"TRY": 14.686145
},
"2022-04-04": {
"TRY": 14.696501
},
"2022-04-05": {
"TRY": 14.72297
}
}
}
this is my object
struct APIResult: Codable {
let timeseries: Bool
let success: Bool
let startDate: String
let endDate: String
let base: String
var rates: [String:[String:Double]]
}
and this is my code inside VC to get the current date and 10 days before and I can see it printed in the console.
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = .current
formatter.locale = .current
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -10, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
lastly these are the codes inside my other file called CurrencyManager
func fetchRatesForTimeframe(from: String, to: String, startDate: String, endDate:String) {
let urlString = "\(timeFrameUrl)base=\(from)&symbols=\(to)&start_date=\(startDate)&end_date=\(endDate)&apikey=\(api.convertApi)"
performRequestforTimeframe(with: urlString)
}
func performRequestforTimeframe(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
} else {
if let safeData = data {
if let timeFrameRates = parseJSONForTimeframe(currencyData: safeData) {
print(timeFrameRates)
self.delegate?.didGetTimeframeRates(timeFrameRates)
}
}
}
}
task.resume()
}
}
func cut(_ value: [String: [String: Double]]) -> [String: [String: Double]] {
let dic = value
.sorted(by: { $0.0 < $1.0 })[0..<10] // <-- last 10 results
.reduce(into: [String: [String: Double]]()) {
$0[$1.key] = $1.value
}
return dic
}
func parseJSONForTimeframe(currencyData: Data) -> APIResult? {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let decoder = JSONDecoder()
var jsondata = try decoder.decode(APIResult.self, from: currencyData)
jsondata.rates = cut(jsondata.rates)
return jsondata
} catch {
return nil
}
}
}
why I can't get the result for print(timeFrameRates) inside func performRequestforTimeframe ? what s missing?

Related

JSON Decoder not working when inserting data into CoreData in Swift

My app consists of a ProductFamily entity with a name attribute and an array of PartDetail dictionaries defined as a one-to-many relationship in CoreData. For each ProductFamily, I can have many PartDetail entities (PartNumbers) but for each PartDetail, it can only be associated with one ProductFamily. My example has 5 ProductFamilies, each with an array of 5 PartDetail dictionaries. I'm struggling to get my JSON decoder correct. It's not importing any data into CoreData. You can clone my sample project here:
https://github.com/jegrasso19/ProductFinder-Test2.git
A sample of my JSON data looks like this:
[
{
"Product Family 1": [
{
"partNumber": "160-9013-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9104-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9105-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9108-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9109-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
},
{
"Product Family 2": [
{
"partNumber": "160-9113-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9114-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9115-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9116-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9201-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
}
]
My ProductFamilyJSON Decoder file and ProductFamilyProperties looks like this:
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
// This is where my code fails. When decoding the JSON file,
// it never goes into the while loop.
var productFamily = try ProductFamilyProperties(from: decoder)
while !rootContainer.isAtEnd {
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailInfo>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailInfo.self) {
partNumbers.append(partNumber)
}
}
productFamily.code = UUID().uuidString
productFamily.name = productFamilyKey.stringValue
productFamily.partNumbers = partNumbers
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}
import Foundation
struct ProductFamilyProperties : Decodable {
var code: String
var name: String
var partNumbers: Array<PartDetailInfo>
enum CodingKeys: String, CodingKey {
case code
case name
case partNumbers
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let rawCode = try? values.decode(String.self, forKey: .code)
let rawName = try? values.decode(String.self, forKey: .name)
let rawPartNumbers = try? values.decode(Array<PartDetailInfo>.self, forKey: .partNumbers)
guard let code = rawCode,
let name = rawName,
let partNumbers = rawPartNumbers
else {
throw myError.programError("Missing Data from Product Family")
}
self.code = code
self.name = name
self.partNumbers = partNumbers
}
var dictionaryValue: [String: Any] {
[
"code": code,
"name": name,
"partNumbers": partNumbers
]
}
}
In my ProductFamilyJSON file, it seems to quit at defining the productFamily variable, which is based on my ProductFamilyProperties. This is apparently wrong but I don't know what it should be defined as. This is my first iOS app I'm trying to develop and learn from. I've spent a while learning CoreData and I've seen so many examples but very few use NSBatchInsertRequest and everyone seems to do this a little differently. I would appreciate some insight on getting this to work. Thanks.
Here is my CoreDataManager class, which contains the NSBatchInsertRequest for reference.
import Foundation
import CoreData
class CoreDataManager: ObservableObject {
let persistentContainer: NSPersistentContainer
static var shared = CoreDataManager()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
private init() {
persistentContainer = NSPersistentContainer(name: "ProductFinderTest")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Unable to initialize Core Data \(error)")
}
}
let directories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
print(directories[0])
}
func newTaskContext() -> NSManagedObjectContext {
let taskContext = persistentContainer.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
taskContext.undoManager = nil
return taskContext
}
}
extension CoreDataManager {
func fetchProductData() async throws {
guard let url = Bundle.main.url(forResource: "ProductFamilies", withExtension: "json"),
let jsonData = try? Data(contentsOf: url)
else {
throw myError.programError("Failed to receive valid response and/or Product Family data.")
}
do {
let jsonDecoder = JSONDecoder()
// ProductFamilyJSON uses this code
let productFamilyJSON = try jsonDecoder.decode(ProductFamilyJSON.self, from: jsonData)
let productFamilyList = productFamilyJSON.productFamilies
print("Received \(productFamilyList.count) Product records.")
print("Start importing product data to the store...")
try await importProductData(from: productFamilyList)
print("Finished importing product data.")
} catch {
throw myError.programError("Wrong Data Format for Product Families")
}
}
private func importProductData(from productList: [ProductFamilyProperties]) async throws {
guard !productList.isEmpty else { return }
let taskContext = newTaskContext()
taskContext.name = "importProductDataContext"
taskContext.transactionAuthor = "importProductData"
try await taskContext.perform {
let batchInsertRequest = self.productListBatchInsertRequest(with: productList)
if let fetchResult = try? taskContext.execute(batchInsertRequest),
let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
else {
throw myError.programError("Failed to execute ProductList batch import request.")
}
}
print("Successfully imported Product data.")
}
private func productListBatchInsertRequest(with productList: [ProductFamilyProperties]) -> NSBatchInsertRequest {
var index = 0
let total = productList.count
let batchInsertRequest = NSBatchInsertRequest(entity: ProductFamily.entity(), dictionaryHandler: { dictionary in
guard index < total else { return true }
dictionary.addEntries(from: productList[index].dictionaryValue)
index += 1
return false
})
return batchInsertRequest
}
func requestProductFamilies() -> NSFetchedResultsController<ProductFamily> {
var fetchedResultsController: NSFetchedResultsController<ProductFamily>!
let request: NSFetchRequest = ProductFamily.fetchProductFamilyRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
managedObjectContext: viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
try? fetchedResultsController.performFetch()
return fetchedResultsController
}
func deleteProductData() async throws {
let taskContext = self.newTaskContext()
let fetchedResultsController = requestProductFamilies()
try fetchedResultsController.performFetch()
let productFamilies = (fetchedResultsController.fetchedObjects ?? []).map(ProductFamilyViewModel.init)
guard !productFamilies.isEmpty else {
print("ProductFamily database is empty.")
return
}
let objectIDs = productFamilies.map { $0.objectId }
print("Start deleting Product data from the store...")
try await taskContext.perform {
let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
let success = batchDeleteResult.result as? Bool, success
else {
throw myError.programError("Failed to execute Product Family batch delete request.")
}
}
print("Successfully deleted Product data.")
}
}
The problem was with how I was initializing the productFamily variable. I needed to initialize it with the actual values instead of as an empty variable. I also needed to move the nestedProductFamilyContainer inside the while loop. Here is the correct ProductFamilyJSON decoder file. In addition, I changed the partNumber attribute in my ProductFamily entity from NSSet to Array, which allowed more flexibility.
#vadian - I did remove the init(from:) and CodingKeys from ProductFamilyProperties as you suggested and it works just fine. Thanks for the input.
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
while !rootContainer.isAtEnd {
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailProperties>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailProperties.self) {
partNumbers.append(partNumber)
}
}
let partNumbersSorted = partNumbers.sorted(by: { $0.partNumber < $1.partNumber })
let productFamily = ProductFamilyProperties(code: UUID().uuidString, name: productFamilyKey.stringValue, partNumbers: partNumbersSorted)
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}

How to parse weather data from NASA API

So, the issue is - I am trying to display the Mars weather from the Mars Insight API. Here is a link Insight Weather, the data is returning in JSON format and has three levels. The keys have names that change depending on the current date (sols). How to make the structure of mutable properties?... when the property names change every day. Do we have any instruments to parse such JSON?
{
"815": {
"First_UTC": "2021-03-12T14:54:38Z",
"Last_UTC": "2021-03-13T15:34:09Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 728.378,
"ct": 153082,
"mn": 708.4211,
"mx": 744.9279
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"818": {
"First_UTC": "2021-03-15T20:01:49Z",
"Last_UTC": "2021-03-16T17:32:54Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 727.696,
"ct": 109855,
"mn": 710.223,
"mx": 743.946
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"819": {
....
,
"sol_keys": [
"815",
"818",
"819",
"820",
"821"
],
"validity_checks": {
"815": {
"PRE": {
"sol_hours_with_data": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
],
"valid": true
}
}
So, Structure:
import Foundation
struct WeatherData: Decodable {
let season: String?
let pre: pre?
let solKeys: [String]
enum CodingKeys: String, CodingKey {
case season = "season"
case pre = "PRE"
case solKeys = "sol_keys"
}
struct pre: Decodable {
let av: Double?
let mn: Double?
let mx: Double?
enum CodingKeys: String, CodingKey {
case av = "av"
case mn = "mn"
case mx = "mx"
}
}
}
Parsing:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
guard let url = URL(string: urlString) else { return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let safeData = data else { return }
do {
let solWeather = try JSONDecoder().decode([String:WeatherData].self, from: safeData)
if let keys = solWeather["sol_keys"] {
for key in keys { //error: For-in loop requires 'WeatherData' to conform to 'Sequence'
let report = solWeather [key]
}
}
print(solWeather)
}
catch let error {
print(error)
}
}.resume()
}
}
Got an error: For-in loop requires 'WeatherData' to conform to 'Sequence'
Don't understand why :(( Can someone help me?
Thanks!
You cannot decode [String:WeatherData].self because the dictionary contains other values which are not WeatherData, for example the [String] value of sol_keys.
The only way to decode this JSON with JSONDecoder is to implement init(with decoder, decode the sol_keys and create your own temporary CodingKeys to be able to decode the arbitrary dictionary keys.
First declare the custom CodingKey
public struct SolKeys: CodingKey {
public let stringValue: String
public init?(stringValue: String) { self.stringValue = stringValue }
public var intValue: Int? { return nil }
public init?(intValue: Int) { return nil }
}
The Decodable structs are
struct SolData : Decodable {
let firstUTC, lastUTC : Date
let pre : Pre
private enum CodingKeys : String, CodingKey {
case firstUTC = "First_UTC", lastUTC = "Last_UTC", pre = "PRE"
}
}
struct Pre: Decodable {
let av, mn, mx : Double
}
struct WeatherData: Decodable {
let solKeys: [String]
var soldata = [String:SolData]()
enum CodingKeys: String, CodingKey {
case solKeys = "sol_keys"
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.solKeys = try container.decode([String].self, forKey: .solKeys)
let customContainer = try decoder.container(keyedBy: SolKeys.self)
for key in solKeys {
let solKey = SolKeys(stringValue: key)!
let data = try customContainer.decode(SolData.self, forKey: solKey)
soldata[key] = data
}
}
}
And the code to receive and decode the data
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let solWeather = try decoder.decode(WeatherData.self, from: data!)
let keys = solWeather.solKeys
for key in keys {
let report = solWeather.soldata[key]!
print(report)
}
}
catch {
print(error)
}
}.resume()

Swift weather data from Openweathermap API?

From openweathermap api, I am getting below response.
{
"cod":"200",
"message":0,
"cnt":40,
"list":[
{
"dt":1587643200,
"main":{
"temp":289.78,
"feels_like":283.61,
"temp_min":289.03,
"temp_max":289.78,
"pressure":1014,
"sea_level":1014,
"grnd_level":1010,
"humidity":41,
"temp_kf":0.75
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
"clouds":{
"all":94
},
"wind":{
"speed":6.75,
"deg":2
},
"sys":{
"pod":"d"
},
"dt_txt":"2020-04-23 12:00:00"
},
{
"dt":1587654000,
"main":{
"temp":289.66,
"feels_like":284.44,
"temp_min":289.34,
"temp_max":289.66,
"pressure":1013,
"sea_level":1013,
"grnd_level":1009,
"humidity":47,
"temp_kf":0.32
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04d"
}
],
"clouds":{
"all":67
},
"wind":{
"speed":5.9,
"deg":357
},
"sys":{
"pod":"d"
},
"dt_txt":"2020-04-23 15:00:00"
}
Then I write the code below to get wind data for any specific daytime(dt). I get the jsonresponse to the Any "list". But I can't get the wind data. I get the error
"Value of type 'Any' has no subscripts".
Also, I can't understand how can I get the wind data for dt=1587643200 and dt=1587654000 separately.
if let list = jsonresponse["list"] as? Any {
let wind = list["wind"] as? [String : Any],
print(wind)
}
This is a super simple example, this question is similar to your problem. I would like that you learn about Codable protocol to simplify and improve your code because of this way is super creepy.
let url = URL(string: "https://samples.openweathermap.org/data/2.5/history/city?id=2885679&type=hour&appid=b1b15e88fa797225412429c1c50c122a1")!
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
guard let data = data else {
return
}
guard let dictionaryObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return
}
guard let list = dictionaryObj["list"] as? [[String: Any]] else {
return
}
if let first = list.first, let wind = first["wind"] {
print(wind)
}
}).resume()
Incase anyone further needs help, I wrote a small code segment—using Swift Codables—to get this done.
Note that I'm using Higher Order Functions in Swift to filter & print the required values. You can follow this tutorial if you need to know more about how they work..
let urlNew = "https://samples.openweathermap.org/data/2.5/history/city?id=2885679&type=hour&appid=b1b15e88fa797225412429c1c50c122a1"
struct WeatherResponse: Codable {
let message: String
let cod: String
let city_id: Int
let calctime: Double
let cnt: Int
let list: [WeatherCompositeObject]
struct WeatherCompositeObject: Codable {
let main: WeatherMainObject
let wind: WeatherWindObject
let clouds: WeatherCloudObject
let weather: [WeatherObject]
let dt: Int
struct WeatherMainObject: Codable {
let temp: Double
let humidity: Int
//You can add the other parameters as needed here
}
struct WeatherWindObject: Codable {
let speed: Double
let deg: Double
}
struct WeatherCloudObject: Codable {
let all: Int
}
struct WeatherObject: Codable {
let id: Int
let main: String
let description: String
}
}
}
class ApiContentDownloader {
func getCurrentWeather(url: String) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, urlResponse, error in
let parser = ContentParser()
let result = parser.parseData(data: data!)
if let result = result {
print("Weather: \(result.message)")
print("DT values: \(result.list.map({ $0.dt})) ")
print("Wind values: \(result.list.map({ $0.wind.speed})) ")
print("Weather descriptions: \(result.list.map({ $0.weather.map( {$0.description} )})) ")
}
else {
print("Oops... Error occured")
}
}.resume()
}
}
class ContentParser {
func parseData(data: Data) -> WeatherResponse? {
var result: WeatherResponse? = nil
do {
let json = try JSONDecoder().decode(WeatherResponse.self, from: data)
result = json
}
catch {
print(error.localizedDescription)
}
return result
}
}
let downloader = ApiContentDownloader()
downloader.getCurrentWeather(url: urlNew)

Swift JSON Parsing Wunderground

i am a beginner with swift an Progrmamming i need values from the JSON it looks Like this:
{
"response": {
"version":"0.1",
"termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
"features": {
"astronomy": 1
}
}
, "moon_phase": {
"percentIlluminated":"23",
"ageOfMoon":"5",
"phaseofMoon":"Erstes Viertel",
"hemisphere":"North",
"current_time": {
"hour":"8",
"minute":"28"
},
"sunrise": {
"hour":"6",
"minute":"55"
},
"sunset": {
"hour":"19",
"minute":"50"
},
"moonrise": {
"hour":"9",
"minute":"50"
},
"moonset": {
"hour":"0",
"minute":"06"
}
},
"sun_phase": {
"sunrise": {
"hour":"6",
"minute":"55"
},
"sunset": {
"hour":"19",
"minute":"50"
}
}
}
i will get the hour & minute from sunset and sunrise to a variable!
my code :
var sunriseHour: [String] = []
var sunriseMinute: [String] = []
var sunsetHour: [String] = []
var sunsetMinute: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
let url=URL(string:"http://api.wunderground.com/api/dbd539bd631e/astronomy/lang:DL/q/DL/iphofen.json")
do {
let allData = try Data(contentsOf: url!)
let data = try JSONSerialization.jsonObject(with: allData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : AnyObject]
if let arrJSON = data["moon_phase"] {
if let sunriseData = arrJSON["sunrise"]{
print(sunriseData)
}
if let sunsetData = arrJSON["sunset"]{
print(sunsetData)
}
}
print("hour: ",sunriseHour)
print("minute: ",sunriseMinute)
print("hour: ",sunsetHour)
print("minute: ",sunsetMinute)
}
catch {
}
}
i get with print(sunriseData) and print(sunsetData) the right data from this :
Optional({
hour = 6;
minute = 55; }) Optional({
hour = 19;
minute = 50; }) hour: [] minute: [] hour: [] minute: []
How do i get it in the variable sunriseHour, sunriseMinute, sunsetHour, sunsetMinute..
thanks from your help..!
All hour and minute values are (single) String not [String] (array).
Declare all variables as empty strings
var sunriseHour = ""
var sunriseMinute = ""
var sunsetHour = ""
var sunsetMinute = ""
Then do not load data synchronously from a remote URL, use always an asynchronous way:
let url = URL(string:"http://api.wunderground.com/api/dbd539bd631e/astronomy/lang:DL/q/DL/iphofen.json")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
} else {
do {
guard let jsonData = try JSONSerialization.jsonObject(with:data!) as? [String:Any],
let moonPhase = jsonData["moon_phase"] as? [String:Any] else { return }
let sunrise = moonPhase["sunrise"] as! [String:String]
let sunset = moonPhase["sunset"] as! [String:String]
sunriseHour = sunrise["hour"]!
sunriseMinute = sunrise["minute"]!
sunsetHour = sunset["hour"]!
sunsetMinute = sunset["minute"]!
print("Sunrise - \(sunriseHour):\(sunriseMinute)"
print("Sunset - \(sunsetHour):\(sunsetMinute)"
} catch {
print(error)
}
}
}
task.resume()
The data for sunrise and sunset are in dictionaries derived from moon_phase. All values are forced unwrapped because sunrise and sunset are supposed to exist every day ;-)
PS : Consider to define more descriptive variable names, arrJSON is not very meaningful.

Parsing json to swift constants

I am trying to parse all the data from the endpoint and assign it to constants so I can use them in my ViewController class. My biggest problem is assigning each item of the "key" array to constants. Can anyone help me out?
static func fetchRates(completionHandler: (current: [Currency]) -> ()) {
let urlString = "https://api.bitcoinaverage.com/ticker/all"
let url = NSURL(string: urlString)
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (location, response, error) -> Void in
do {
let json = try(NSJSONSerialization.JSONObjectWithData(location!, options: .MutableContainers))
let tickerData = [Currency]()
for key in json as! [String : AnyObject] {
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completionHandler(current: tickerData)
})
} catch let err as NSError {
print(err)
}
}).resume()
}
This is the response from the endpoint.
{
"AUD": {
"24h_avg": 621.17,
"ask": 624.12,
"bid": 620.3,
"last": 620.45,
"timestamp": "Mon, 23 May 2016 20:01:16 -0000",
"total_vol": 671.28
},
"BRL": {
"24h_avg": 1725.77,
"ask": 1748.83,
"bid": 1731.8,
"last": 1738.64,
"timestamp": "Mon, 23 May 2016 20:01:16 -0000",
"total_vol": 534.19
},
"CAD": {
"24h_avg": 579.2,
"ask": 579.27,
"bid": 573.57,
"last": 577.42,
"timestamp": "Mon, 23 May 2016 20:01:16 -0000",
"total_vol": 413.81
},
Give this a try:
for (key, value) in (json as! [String: AnyObject]) {
let currencyData = value as! [String: AnyObject]
// key is the currency code: AUD, BRL, etc
// currencyData is the details: ask, bid, last, ...
}
Here's a rough and ready solution, but it's a start you can build on.
Let's start by defining some convenience types:
// Define a handy result type
// See https://github.com/antitypical/Result if you want a more detailed implementation.
public enum Result<T> {
case Success(T)
case Error(ErrorType)
}
// Define an error type for inconsistentData
public enum DataError: ErrorType {
case NoData
}
// Define a struct to hold a Currency
public struct Currency: CustomStringConvertible, Equatable {
let currencyCode: String
let dayAverage: Double
let ask: Double
let bid: Double
let last: Double
let timestamp: String
let totalVolume: Double
// This makes it easier to debug
public var description: String {
return "currencyCode: \(currencyCode), dayAverage: \(dayAverage), ask: \(ask), bid: \(bid), last: \(last), timestamp: \(timestamp), totalVolume: \(totalVolume)"
}
public init(currencyCode: String,
dayAverage: Double,
ask: Double,
bid: Double,
last: Double,
timestamp: String,
totalVolume: Double) {
self.currencyCode = currencyCode
self.dayAverage = dayAverage
self.ask = ask
self.bid = bid
self.last = last
self.timestamp = timestamp
self.totalVolume = totalVolume
}
}
// Make sure your structs conform to Equatable.
public func == (lhs: Currency, rhs: Currency) -> Bool {
return
lhs.currencyCode == rhs.currencyCode &&
lhs.dayAverage == rhs.dayAverage &&
lhs.ask == rhs.ask &&
lhs.bid == rhs.bid &&
lhs.last == rhs.last &&
lhs.timestamp == rhs.timestamp &&
lhs.totalVolume == rhs.totalVolume
}
// I like to define convenience initialisers outside of the struct. Keeps things less coupled.
public extension Currency {
public init?(currencyCode: String, values: [String : AnyObject]) {
guard
let dayAverage = values["24h_avg"] as? Double,
let ask = values["ask"] as? Double,
let bid = values["bid"] as? Double,
let last = values["last"] as? Double,
let timestamp = values["timestamp"] as? String,
let totalVolume = values["total_vol"] as? Double
else { return nil }
self = Currency(currencyCode: currencyCode,
dayAverage: dayAverage,
ask: ask,
bid: bid,
last: last,
timestamp: timestamp,
totalVolume: totalVolume)
}
}
Now the function to fetch the values can be written as:
// The function to fetch the currencies.
// Use a richer type for the completion parameter. It is either a success with a list of currencies or an error.
public func fetchRates(completionHandler: (Result<[Currency]>) -> ()) {
let url = NSURL(string: "https://api.bitcoinaverage.com/ticker/all")! // Force unwrapped as we assume this is a valid URL
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, _, error) in
if let error = error {
completionHandler(.Error(error))
return
}
do {
guard
let data = data,
// Being generous with the type of the result because there is a top level key that is the timestamp not related to a currency.
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers) as? [String : AnyObject]
else {
completionHandler(.Error(DataError.NoData))
return
}
var currencies = [Currency]()
for (key, value) in json {
guard
// Make sure that the values we are about to pass on are of the correct type.
let value = value as? [String : AnyObject],
let currency = Currency(currencyCode: key, values: value)
else { continue }
currencies.append(currency)
}
print(currencies)
completionHandler(.Success(currencies))
} catch {
completionHandler(.Error(error))
}
}.resume()
}
And you can run this function with the completion handlers as:
fetchRates { result in
dispatch_async(dispatch_get_main_queue()) {
switch result {
case .Success(let currencies):
for currency in currencies {
print(currency)
}
case .Error(let error):
print(error)
}
}
}
You can download the playground where you can see all this working at:
https://dl.dropboxusercontent.com/u/585261/Currency.playground.zip