Issues with Swift valueNotFound during API Call - ios

I am looking for some help working with a Swift project. I am building an app that pulls aviation weather data from an API and this situation is common:
User wants data from airport weather station KDAB - current report says:
Wind 10 kt
Scattered Clouds 2500 ft
Visibility 10 SM
Light Rain
User wants data from airport weather station KJAX - current report says:
Wind 16 kt
Wind Gust 24 kt
Broken Clouds 1400 ft
Scattered Clouds 1900 ft
Few clouds 2400 ft
In this simple example, you may notice that there is no wind gust data supplied for KJAX during this reporting period and no "special weather" (ie rain, haze, fog) specified for KDAB. My app needs to be able to handle "nil" or not provided data without just telling me that there is a valueNotFound or that the index is out of range.
Here are the API Docs: https://avwx.docs.apiary.io/#reference/0/metar/get-metar-report
Here is my code:
import Foundation
struct WeatherManager {
let weatherURL = "https://avwx.rest/api/metar/"
func fetchWeather (stationICAO: String) {
let urlString = "\(weatherURL)\(stationICAO)?token=OVi45FiTDo1LmyodShfOfoizNe5m9wyuO6Mkc95AN-c"
performRequest(urlString: urlString)
}
func performRequest (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!)
return
}
if let safeData = data {
self.parseJSON(weatherData: safeData)
}
}
task.resume()
print(urlString)
}
}
func parseJSON(weatherData: Data) {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
let lowCloudsType = decodedData.clouds[0].type
let midCloudsType = decodedData.clouds[1].type
let highCloudsType = decodedData.clouds[2].type
let lowCloudsAlt = decodedData.clouds[0].altitude
let midCloudsAlt = decodedData.clouds[1].altitude
let highCloudsAlt = decodedData.clouds[2].altitude
let reportingStationVar = decodedData.station
let windGustValue = decodedData.wind_gust.value
let windSpeedValue = decodedData.wind_speed.value
let windDirectionValue = decodedData.wind_direction.value
let visibilityValue = decodedData.visibility.value
let flightRulesValue = decodedData.flight_rules
let weather = WeatherModel(lowestCloudsType: lowCloudsType, lowestCloudsAlt: lowCloudsAlt, middleCloudsType: midCloudsType, middleCloudsAlt: midCloudsAlt, highestCloudsType: highCloudsType, highestCloudsAlt: highCloudsAlt, reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue)
print(weather.flightConditions)
} catch {
print(error)
}
}
}
import Foundation
struct WeatherModel {
let lowestCloudsType: String
let lowestCloudsAlt: Int
let middleCloudsType: String
let middleCloudsAlt: Int
let highestCloudsType: String
let highestCloudsAlt: Int
let reportingStation: String
let windGust: Int
let windSpeed: Int
let windDirection: Int
let visibility: Int
let flightRules: String
var flightConditions: String {
switch flightRules {
case "VFR":
return "green"
case "MVFR":
return "blue"
case "IFR":
return "red"
case "LIFR":
return "purple"
default:
return "gray"
}
}
}
Last One:
import Foundation
struct WeatherData: Decodable {
let clouds: [Clouds]
let flight_rules: String
let remarks: String
let wind_speed: WindSpeed
let wind_gust: WindGust
let wind_direction: WindDirection
let visibility: Visibility
let station: String
}
struct Clouds: Decodable {
let type: String
let altitude: Int
}
struct WindSpeed: Decodable {
let value: Int
}
struct WindGust: Decodable {
let value: Int
}
struct WindDirection: Decodable {
let value: Int
}
struct Visibility: Decodable {
let value: Int
}
Depending on what I play with, I get the following errors when entering a station that does not have all of that given information that I need to be able to present to the user if reported by the weather service.
2020-09-22 02:47:58.930421-0400 AvWx Pro[66612:4483807] libMobileGestalt MobileGestaltCache.c:38: No persisted cache on this platform.
KDAB
https://avwx.rest/api/metar/KDAB?token=(mySecretToken)
2020-09-22 02:48:02.943231-0400 AvWx Pro[66612:4483809] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
valueNotFound(Swift.KeyedDecodingContainer<AvWx_Pro.WindGust.(unknown context at $1053fb3b8).CodingKeys>,
Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "wind_gust", intValue: nil)],
debugDescription: "Cannot get keyed decoding container
-- found null value instead.", underlyingError: nil))
A different error when I use an airport that doesn't have all three of the possible cloud layers reported:
2020-09-22 03:06:02.398628-0400 AvWx Pro[66736:4497432] libMobileGestalt MobileGestaltCache.c:38: No persisted cache on this platform.
KJAX
https://avwx.rest/api/metar/KJAX?token=(mySecretKey)
2020-09-22 03:06:07.955064-0400 AvWx Pro[66736:4497429] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1200.2.22.2/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
2020-09-22 03:06:08.908826-0400 AvWx Pro[66736:4497429] Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1200.2.22.2/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
(lldb)
I've spent a few hours now trying various solutions I've found online, including using optionals and force unwrapping, using guard let, using if let, and a few others. I'm pretty lost at the moment.
I am new to this platform (as a poster) and would really appreciate any insight anyone can offer! Thanks for the help in advance.

To avoid crashes while decoding json you should make correct configuration of your structs and check the fields before access values:
Optional fields
[CodingKeys(stringValue: "wind_gust", intValue: nil)],
debugDescription: "Cannot get keyed decoding container -- found null
value instead.", underlyingError: nil))
wind_gust can be empty so you should make it optional:
If a field can be empty or null in responses you should make it optional in your struct e.g.:
struct WeatherData: Decodable {
...
let wind_gust: WindGust?
...
}
Then in your code just use optional binding to extract a value if wind_gust exists:
if let value = decodedData.wind_gust?.value {
print(value)
}
Arrays
Fatal error: Index out of range
You must always check an array's bounds before access to items e.g.:
let clouds = decodedData.clouds
let lowCloudsType = clouds.count > 0 ? clouds[0].type : nil
let midCloudsType = clouds.count > 1 ? clouds[1].type : nil
let highCloudsType = clouds.count > 2 ? clouds[2].type : nil
if let low = lowCloudsType, let mid = midCloudsType, let high = highCloudsType {
print(low)
print(mid)
print(high)
}

The answer is to use Optionals
Consider the below playground example, the age and location properties are optional, the location property is missing from the JSON.
import Foundation
struct TestModel: Decodable {
let name: String
let age: Int?
let location: String?
enum CodingKeys: String, CodingKey {
case name
case age
case location
}
}
let jsonData = """
{
"name": "John Smith",
"age": 28
}
""".data(using: .utf8)!
let result = try JSONDecoder().decode(TestModel.self, from: jsonData)
print(result)
Output
TestModel(name: "John Smith", age: Optional(28), location: nil)

Related

JSON Decoding error pops up, dunno what else to try

guys, I would really appreciate some help with JSON decoding. I am trying get API from this link: http://newsapi.org/v2/top-headlines?apiKey=a16b15f863454928804e218705d0f019+&country=us
I might have made some really amateur mistakes. First time uploading a problem here. TY for help!
Here is my DataManager
protocol NewsManagerDelegate {
func didUpdateNews(news: NewsModel)
}
import Foundation
struct NewsManager{
var delegate: NewsManagerDelegate?
let url = "https://newsapi.org/v2/top-headlines?apiKey=a16b15f863454928804e218705d0f019"
func fetchNews(_ countryName: String){
let newsUrlString = "\(url)+&country=\(countryName)"
performRequest(newsUrlString)
}
func performRequest(_ 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("networking error \(error!)")
return
}
if let safeData = data{
if let news = parseJSON(safeData){
delegate?.didUpdateNews(news: news)
}
}
}
task.resume()
}
}
func parseJSON(_ newsData: Data) -> NewsModel?{
do{
let decodedData = try JSONDecoder().decode(NewsData.self, from: newsData)
let sourceName = decodedData.articles[5].source.name
let titleName = decodedData.articles[5].title
let linkToImage = decodedData.articles[5].urlToImage
let news = NewsModel(sourceName: sourceName, titleName: titleName, linkToImage: linkToImage )
return news
}catch{
print(error)
return nil
}
}
}
and my Data
import Foundation
struct NewsData: Codable {
let totalResults: Int
let articles: [Articles]
}
struct Articles: Codable{
let author: String?
let title: String
let description: String
let urlToImage: String
let source: Source
}
struct Source: Codable{
let name: String
}
I am receiving this error
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "articles", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "description", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
I tried to make some of the constants optional but after that no info would show up at all.
I would just change the property name to articleDescription and make it optional. If you would like to parse the publishedAt date all you need is to set decoder dateDecodingStrategy property to .iso8601. Besides that you are not constructing your url correctly. You should always use URLComponents when composing your url and change your urls properties types from String to URL:
struct Root: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url, urlToImage: URL
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title, articleDescription = "description", url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
Playground testing:
func fetchNews(_ countryCode: String) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "newsapi.org"
urlComponents.path = "/v2/top-headlines"
urlComponents.queryItems = [.init(name: "apiKey", value: "a16b15f863454928804e218705d0f019"),
.init(name:"country", value: countryCode)]
if let url = urlComponents.url {
performRequest(url)
}
}
func performRequest(_ url: URL) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("networking error", error ?? "nil")
return
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let root = try decoder.decode(Root.self, from: data)
let articles = root.articles
for article in articles {
print("article:", article, terminator: "\n")
}
} catch {
print(error)
}
}.resume()
}
fetchNews("us")
This will print:
article: Article(source: __lldb_expr_111.Source(id: Optional("cnn"), name: "CNN"), author: Optional("Oliver Darcy, CNN Business"), title: "Tucker Carlson backlash tells us something important about some Trump supporters - CNN", articleDescription: nil, url: https://www.cnn.com/2020/11/21/media/tucker-carlson-fox-news-traitor/index.html, urlToImage: https://cdn.cnn.com/cnnnext/dam/assets/201105014450-tucker-carlson-fox-news-presidential-election-fraud-super-tease.jpg, publishedAt: 2020-11-21 16:11:00 +0000, content: nil)
article: Article(source: __lldb_expr_111.Source(id: Optional("usa-today"), name: "USA Today"), author: Optional("Joel Shannon, Grace Hauck"), title: "Coronavirus updates: Donald Trump Jr. tests positive; model estimates 471k US deaths by March; Cuomo to receive International Emmy - USA TODAY", articleDescription: Optional("Donald Trump Jr. tests positive for the coronavirus. Model predicts more deaths. Thanksgiving during a pandemic happened before. Latest COVID news."), url: https://www.usatoday.com/story/news/health/2020/11/21/covid-news-donald-trump-jr-positive-thanksgiving-travel-not-advised/6367181002/, urlToImage: https://www.gannett-cdn.com/presto/2020/11/21/NSTT/caa3ba8a-8e3c-4b49-82e2-2810caf33d05-Testing3.jpg?crop=1574,886,x0,y80&width=1600&height=800&fit=bounds, publishedAt: 2020-11-21 15:56:15 +0000, content: Optional("A coronavirus vaccine might not be widely available until several months into 2021.\r\nUSA TODAY\r\nThe U.S reported a record high of more than 195,000 new daily cases of COVID-19 Friday, the same week t… [+11662 chars]"))
article: Article(source: __lldb_expr_111.Source(id: nil, name: "CBS Sports"), author: Optional(""), title: "Clemson vs. Florida State game postponed hours before kickoff as teams disagreed about whether to play - CBS Sports", articleDescription: Optional("A Clemson player's late positive test for COVID-19 is the reason for the abrupt postponement"), url: https://www.cbssports.com/college-football/news/clemson-vs-florida-state-game-postponed-hours-before-kickoff-as-teams-disagreed-about-whether-to-play/, urlToImage: https://sportshub.cbsistatic.com/i/r/2019/01/31/3cfd9702-8ef4-4f0d-9cc6-2601f4b3ad9c/thumbnail/1200x675/d00f9bb392291c844de43984526b773e/clemson.jpg, publishedAt: 2020-11-21 15:28:00 +0000, content: Optional("No. 4 Clemson and Florida State were set to kick off at noon ET in Tallahassee, Florida, a game that would have marked Tigers quarterback Trevor Lawrence's return to action following a positive COVID… [+3139 chars]")) ......
articles - description
"Expected String value but found null instead."
Change
let description: String
to
let description: String?
It says there is no value at the first value, can you post the json response what you're getting as that would be useful. Another possibility could be that the json response is in a different order than what you are decoding.
You could just change description to an optional as that would get rid of the problem but then you wouldn't have a description so it doesn't fully solve the problem
let description: String?

how to add information in an array with a class Swift

I've got a problem for adding some informations in an array.
My class Flights is define by the following :
class Flight{
let date: String
let type: String
let regi: String
let totalTime: String
let depTime: String
let depPlace: String
let arrTime: String
let arrPlace: String
init(from dat: String, _ typ: String, _ reg: String, _ totaltim: String, _ depTim: String, _ depPlac: String, _ arrTim: String, _ arrPlac: String) {
self.date = dat
self.type = typ
self.regi = reg
self.totalTime = totaltim
self.depTime = depTim
self.depPlace = depPlac
self.arrTime = arrTim
self.arrPlace = arrPlac
}}
In my main code I've got declare my array like this :
var datas: [Flight] = []
And finally I've this code to add some informations coming from firebase :
(I add some comment to show you what print() result)
if let user = Auth.auth().currentUser{
// user is connect
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let ev = ref.child("flights").child(userID!)
ev.observe(.childAdded, with: { (snapshot) -> Void in
let flightKey = snapshot.key
ref.child("flights").child(userID!).child(flightKey).observeSingleEvent(of: .value) {(snapshot) in
let value = snapshot.value as? NSDictionary
let date = value?["Date"] as? String ?? "no date"
let type = value?["aircraft-model"] as? String ?? "no type"
let registration = value?["aircraft-registration"] as? String ?? "no callsign"
let totalTime = value?["TOTAL-TIME"] as? String ?? "no total Time"
let deppartTime = value?["departure-time"] as? String ?? "no departure Time"
let deppartPlace = value?["departure-place"] as? String ?? "no departure Place"
let arrivalTime = value?["arrival-time"] as? String ?? "no arrival Time"
let arrivalPlace = value?["arrival-place"] as? String ?? "no arrival Place"
print("Date : \(date) - type : \(type) - registration : \(registration) - Etc ...")// Give me exactly the value I requested
self.datas.append(Flight(from: date, type, registration, totalTime, deppartTime, deppartPlace, arrivalTime, arrivalPlace))
print(self.datas)// Give me "MyProjectName.Flight ...
}
})
}else{
// si non connecté alors DECONNEXION !!!!
fatalError("error ...")
}
So I don't understand why if I print the received value from firebase it work but if I print the array value which is completed by the firebase received value it didn't work ?
Thanks for your help !
Flyer-74
Welcome :)
I think all is as expected and you're just seeing this because Swift doesn't know how to describe your objects.
To fix this, you should implement the CustomStringConvertible protocol in your Flight class (https://developer.apple.com/documentation/swift/customstringconvertible)
So something like
extension Flight: CustomStringConvertible {
var description: String {
var description = ""
description.append("date: \(date)\n")
description.append("type: \(type)\n")
description.append("regi: \(regi)\n")
//and so on
return description
}
}
Should give you what you are looking for.
Hope that helps you
You can try to adopt CustomStringConvertible protocol
class Flight : CustomStringConvertible {
var description: String {
return "\(date) \(type)" // add here any variable you want it to be printed
}
let date: String
let type: String
let regi: String
let totalTime: String
let depTime: String
let depPlace: String
let arrTime: String
let arrPlace: String
init(from dat: String, _ typ: String, _ reg: String, _ totaltim: String, _ depTim: String, _ depPlac: String, _ arrTim: String, _ arrPlac: String) {
self.date = dat
self.type = typ
self.regi = reg
self.totalTime = totaltim
self.depTime = depTim
self.depPlace = depPlac
self.arrTime = arrTim
self.arrPlace = arrPlac
}
}
You could add a custom debug description for your object by adding an extension to Flight, and make it conform to the CustomDebugStringConvertible protocol. Conformance to this protocol requires that you provide a property: var debugDescription: String { get }. Inside this string is where you have full control over the debug values for your custom Object.
extension Flight: CustomDebugStringConvertible {
var debugDescription: String {
return "Date: \(date), Type: \(type), Registartion: \(regi)"
}
}

How to display JSON data from API? [duplicate]

This question already has an answer here:
Expected to decode Array<Any> but found a dictionary instead
(1 answer)
Closed 4 years ago.
I am developing in Swift 4 and currently using Walmart's API in order to display their products and certain information about the products (example: name of product, price of product). I have read and watched many tutorials regarding parsing JSON data however I continue to get the same error. If anyone could tell me why im getting an error it would be highly appreciated seeing I have been stuck on this issue for days.
Here is the JSON data I am getting from the API call:
{
query: "ipod",
sort: "relevance",
format: "json",
responseGroup: "base",
totalResults: 3570,
start: 1,
numItems: 10,
items: [
{
itemId: 15076191,
parentItemId: 15076191,
name: "Apple iPod Touch 4th Generation 32GB with Bonus Accessory Kit",
salePrice: 189
}
I just want to display the name and salePrice data but I am unable to do so at the moment, instead I get this error: typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
Here is my data model:
struct Product: Codable {
let name: String
let salePrice: String
}
Here is the code in my ViewController class:
class ViewController: UIViewController {
import Foundation
import UIKit
var products: [Product]?
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "http://api.walmartlabs.com/v1/search?query=sauce&format=json&apiKey=xyz"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let productData = try JSONDecoder().decode([Product].self, from: data)
print(productData)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
}
It will be like this,
struct Item: Codable {
let query: String
let sort: String
let responseGroup: String
let totalResults: Int
let start: Int
let numItems: Int
let items: [Product]
}
struct Product: Codable {
let name: String
let salePrice: CGFloat
}
Try using this,
let productData = try JSONDecoder().decode(Item.self, from: data)
Your json data is a dictionary not an array you either parse it and get the array , or try this
struct Item: Codable {
let query: String
let sort: String
let format: String
let responseGroup: String
let totalResults: Int
let start: Int
let numItems: Int
let items: [Product]
}
struct Product: Codable {
let itemId: Double
let parentItemId: Double
let name: String
let salePrice: Int
}
let productData = try JSONDecoder().decode(Item.self, from: data)

JSON Parsing Help in Swift 4 - Data Structure Issue?

New on here and to Swift so please go easy on me..
Am a bit stuck when trying to parse JSON which contains nested dictionaries. I imagine its something wrong with the data strutures I have created and I have tryed everthing to rectify but still getting the same issue.
This is the JSON api I am trying to work with:
https://api.coindesk.com/v1/bpi/currentprice.json
These are the data structures I have created to model this:
struct base: Decodable {
let disclaimer: String
let bpi: [Bpi]
}
struct Bpi: Decodable {
let USD: [USD]
}
struct USD: Decodable {
let rate_float: Float
}
And here is my code in the VC :
override func viewDidLoad() {
super.viewDidLoad()
let jsonURLString = "https://api.coindesk.com/v1/bpi/currentprice.json"
guard let url = URL(string: jsonURLString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let bitcoinData = try JSONDecoder().decode(base.self, from: data)
print(bitcoinData.bpi)
} catch {
print("error")
}
} .resume() // Fires off the session
}
I can grab the data from the disclaimer string or the other strings in the root dictionary but that is it. I cannot parse anything further with the nested dictonaries - it just throws back the catch error.
Here is the JSON:
{
"time": {
"updated": "Nov 2, 2017 06:08:00 UTC",
"updatedISO": "2017-11-02T06:08:00+00:00",
"updateduk": "Nov 2, 2017 at 06:08 GMT"
},
"disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
"chartName": "Bitcoin",
"bpi": {
"USD": {
"code": "USD",
"symbol": "$",
"rate": "6,889.4013",
"description": "United States Dollar",
"rate_float": 6889.4013
},
"GBP": {
"code": "GBP",
"symbol": "£",
"rate": "5,184.4053",
"description": "British Pound Sterling",
"rate_float": 5184.4053
},
"EUR": {
"code": "EUR",
"symbol": "€",
"rate": "5,910.4587",
"description": "Euro",
"rate_float": 5910.4587
}
}
}
Is there something I am clearly doing wrong here?
Thanks for the help in advance and sorry if my formatting sucks!
Try following model, with this it works - both bpi and USD are not arrays, just single values:
struct base: Decodable {
let disclaimer: String
let bpi: Bpi
}
struct Bpi: Decodable {
let USD: USD
}
struct USD: Decodable {
let rate_float: Float
}
Dictionaries (Dictionary<K,V>) are implicitly Decodable compliant if both generic types K and V are decodable.
Assuming you create a struct Coin for the currencies
struct Coin: Decodable {
private enum CodingKeys : String, CodingKey {
case code, symbol, rate, description, rateFloat = "rate_float"
}
let code : String
let symbol : String
let rate : String
let description : String
let rateFloat : Float
}
you can easily decode the currency dictionaries as [String:Coin] without any additional code
struct Base: Decodable {
private enum CodingKeys : String, CodingKey {
case disclaimer, coins = "bpi"
}
let disclaimer: String
let coins: [String:Coin]
}
And use it
let bitcoinData = try JSONDecoder().decode(Base.self, from: data)
print(bitcoinData.coins)
Alternatively if you want the currencies as an array of Coin you can write a custom initializer and map the dictionary values to an array.
This example decodes also the updatedISO value in the time dictionary
struct Base: Decodable {
struct Time : Decodable {
private enum CodingKeys : String, CodingKey {
case updated = "updatedISO"
}
let updated : Date
}
private enum CodingKeys : String, CodingKey {
case disclaimer, bpi, time
}
let disclaimer: String
let coins: [Coin]
let updated : Date
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
disclaimer = try container.decode(String.self, forKey: .disclaimer)
let bpi = try container.decode([String:Coin].self, forKey: .bpi)
coins = Array(bpi.values.sorted(by: {$0.code < $1.code}))
let time = try container.decode(Time.self, forKey: .time)
updated = time.updated
}
}
And use this example
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let bitcoinData = try decoder.decode(Base.self, from: data)
print(bitcoinData.coins)
You declared your bpi & use properties as arrays but they were dictionaries (nested json objects). If you have sample JSON you can try this converter next time: https://danieltmbr.github.io/JsonCodeGenerator/
It generates the following output:
struct Root: Codable {
let time: Time
let disclaimer: String
let chartName: String
let bpi: Bpi
}
struct Time: Codable {
let updated: String
let updatedISO: String
let updateduk: String
}
struct Bpi: Codable {
let USD: USD
let GBP: USD
let EUR: USD
}
struct USD: Codable {
let code: String
let symbol: String
let rate: String
let description: String
let rateFloat: Double
private enum CodingKeys: String, CodingKey {
case code
case symbol
case rate
case description
case rateFloat = "rate_float"
}
}

How do I create an array of objects based on array of JSON objects using SwiftyJSON (in Swift)?

Here's the JSON I am getting from the server:
{
"items": [
{
"name": "Shampoo",
"price": 9
},
...
]
}
Here's my Item class in Swift:
class Item {
var name: String
var price: Float
init(name: String, price: Float) {
self.name = name
self.price = price
}
}
I want to create an Item object for each JSON object in the items array using SwiftyJSON. So I thought I'd just loop through the Swift array that SwiftyJSON will create for me and voila. But SwiftyJSON throws an error saying items is not an array. I tried subscripting it as a dictionary but you can't (I thought you could) iterate through a dictionary in a for-loop.
Here's the code I have tried:
let json = JSON(data: data) // data is the JSON from the server (above) and isn't nil
let items = json["items"].array // this is nil and where SwiftyJSON throws the error.
// error checking and optional unwrapping etc.
for item in items {
var itemsList: [Item] = []
itemsList.append(Item(name: item["name"], price: item["price"]))
}
I feel like this should be pretty easy so if anyone can find where I went wrong I'd really appreciate it. Thanks!
Check out ObjectMapper, it is another JSON parser library for swift.
It support mapping an array out of the box.
Just declare your server response object like:
class ServerResponse: Mappable {
var array: [Item]?
required init?(_ map: Map) {
}
// Mappable
func mapping(map: Map) {
array <- map["items"]
}
}
This is how i do in my project...
guard let cityName = json["city"]["name"].string else {return}
guard let cityID = json["city"]["id"].int else {return}
var allForecasts = [Forecast]()
guard let allStuff = json["list"].array else {return}
for f in allStuff {
guard let date = f["dt"].double else {continue}
let dateUnix = NSDate(timeIntervalSince1970: date)
guard let temp = f["main"]["temp"].double else {continue}
guard let tempMin = f["main"]["temp_min"].double else {continue}
guard let tempMax = f["main"]["temp_max"].double else {continue}
guard let pressure = f["main"]["pressure"].double else {continue}
guard let humidity = f["main"]["humidity"].double else {continue}
guard let description = f["weather"][0]["description"].string else {continue}
guard let icon = f["weather"][0]["icon"].string else {continue}
guard let wind = f["wind"]["speed"].double else {continue}
let weather = Forecast(temperature: temp, maximum: tempMax, minimum: tempMin, description: description, icon: icon, humidity: humidity, pressure: pressure, wind: wind, date: dateUnix)
allForecasts.append(weather)
}
let fullWeather = City(cityID: cityID, cityName: cityName, forecasts: allForecasts)
I think it's helpful.

Resources