OpenWeatherMap and Swift 4 - json

I am trying to build a simple weather app using OpenWeatherMap APIs in Swift 4.
I can parse Json data in simple cases, but this one has a more complex structure.
This is the Json file the API returns.
{"coord":{"lon":144.96,"lat":-37.81},"weather":[{"id":520,"main":"Rain","description":"light
intensity shower
rain","icon":"09n"}],"base":"stations","main":{"temp":288.82,"pressure":1019,"humidity":100,"temp_min":288.15,"temp_max":289.15},"visibility":10000,"wind":{"speed":4.1,"deg":200},"clouds":{"all":90},"dt":1544284800,"sys":{"type":1,"id":9548,"message":0.5221,"country":"AU","sunrise":1544208677,"sunset":1544261597},"id":2158177,"name":"Melbourne","cod":200}
I created a few Struct(s) to get the Json data.
struct CurrentLocalWeather: Decodable {
let base: String
let clouds: Clouds
let cod: Int
let coord: Coord
let dt: Int
let id: Int
let main: Main
let name: String
let sys: Sys
let visibility: Int
let weather: [Weather]
let wind: Wind
}
struct Clouds: Decodable {
let all: Int
}
struct Coord: Decodable {
let lat: Double
let lon: Double
}
struct Main: Decodable {
let humidity: Int
let pressure: Int
let temp: Double
let tempMax: Int
let tempMin: Int
private enum CodingKeys: String, CodingKey {
case humidity, pressure, temp, tempMax = "temp_max", tempMin = "temp_min"
}
}
struct Sys: Decodable {
let country: String
let id: Int
let message: Double
let sunrise: UInt64
let sunset: UInt64
let type: Int
}
struct Weather: Decodable {
let description: String
let icon: String
let id: Int
let main: String
}
struct Wind: Decodable {
let deg: Int
let speed: Double
}
To use those datas this is the code I wrote:
let url = "https://api.openweathermap.org/data/2.5/weather?q=melbourne&APPID=XXXXXXXXXXXXXXXX"
let objurl = URL(string: url)
URLSession.shared.dataTask(with: objurl!) {(data, response, error) in
do {
let forecast = try JSONDecoder().decode([CurrentLocalWeather].self, from: data!)
for weather in forecast {
print(weather.name)
}
} catch {
print("Error")
}
}.resume()
That should print the city name in the console.
Unfortunately it prints Error.

You need
let forecast = try JSONDecoder().decode(CurrentLocalWeather.self, from: data!)
print(forcast.name)
as the root is a dictionary not array

Related

API Decoding Struct Swift

Hello I'm trying to decode this API, https://gorest.co.in/public-api/posts. What I'm doing wrong with structs? Thank you.
struct Root: Decodable {
let first: [Code]
let second: [Meta]
let third: [Post] }
struct Code: Decodable {
let code: Int
}
struct Meta: Decodable {
let meta: [Pagination]
}
struct Pagination: Decodable {
let total: Int
let pages: Int
let page: Int
let limit: Int
}
struct Post: Decodable {
let id: Int
let user_id: Int
let title: String
let body: String
}
Quicktype.io is your friend. Post the JSON to it and get the struct back automatically.
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let root = try? newJSONDecoder().decode(Root.self, from: jsonData)
import Foundation
// MARK: - Root
struct Root: Codable {
let code: Int
let meta: Meta
let data: [Datum]
}
// MARK: - Datum
struct Datum: Codable {
let id, userID: Int
let title, body: String
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case title, body
}
}
// MARK: - Meta
struct Meta: Codable {
let pagination: Pagination
}
// MARK: - Pagination
struct Pagination: Codable {
let total, pages, page, limit: Int
}

Xcode confused with IQAir Api Parsing

so I use I am trying to parse through this data:
{"status":"success","data":{"city":"Sunnyvale","state":"California","country":"USA","location":{"type":"Point","coordinates":[-122.03635,37.36883]},"current":{"weather":{"ts":"2020-07-23T00:00:00.000Z","tp":25,"pr":1009,"hu":44,"ws":6.2,"wd":330,"ic":"02d"},"pollution":{"ts":"2020-07-23T00:00:00.000Z","aqius":7,"mainus":"p2","aqicn":2,"maincn":"p2"}}}}
I am trying to get a hold of the aqius result, as well as the tp...
Here is my code right now, I have created these structs:
struct Response: Codable{
let data: MyResult
let status: String
}
struct MyResult: Codable {
let city: String
}
As you can see, I have gotten city, and I can confirm it works because when I get the request and print(json.data.city) it prints "Sunnyvale".
But how would I get the other values? I have been stuck on how to obtain values within the location , current and pollution data structures, how would I do this?
Thanks
There are tools that automatically generate Codable models from json string like: https://app.quicktype.io)
So your base struct models looks like below;
// MARK: - Response
struct Response: Codable {
let status: String
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let city, state, country: String
let location: Location
let current: Current
}
// MARK: - Current
struct Current: Codable {
let weather: Weather
let pollution: Pollution
}
// MARK: - Pollution
struct Pollution: Codable {
let ts: String
let aqius: Int
let mainus: String
let aqicn: Int
let maincn: String
}
// MARK: - Weather
struct Weather: Codable {
let ts: String
let tp, pr, hu: Int
let ws: Double
let wd: Int
let ic: String
}
// MARK: - Location
struct Location: Codable {
let type: String
let coordinates: [Double]
}
Decode;
let jsonData = jsonString.data(using: .utf8)!
let model = try? JSONDecoder().decode(Response.self, from: jsonData)

I need to create a table view with a nested Json

I have to read a json from the url: https://randomuser.me/api/?results=100
I created the People.swift file that contains the structure created through the site: https://app.quicktype.io/?l=swift
I tried to use this code but I can not then insert the json into the structure and then recall it via cell.people.name for example.
ViewController.swift:
var dataRoutine = [People]() // this is the structure that I created with the site indicated above.
this one is my function to download the Json and parse.
func downloadJsonData(completed : #escaping ()->()){
guard let url = URL(string: "https://randomuser.me/api/?results=100")else {return}
let request = URLRequest.init(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
let statuscode = httpResponse.statusCode
if statuscode == 404{
print( "Sorry! No Routine Found")
}else{
if error == nil{
do{
self.dataRoutine = try JSONDecoder().decode(People.self, from: data!)
DispatchQueue.main.async {
completed()
print(self.dataRoutine.count) // I don't know why my result is ever 1.
}
}catch{
print(error)
}
}
}
}
}.resume()
}
my structure is :
import Foundation
struct People: Codable {
let results: [Result]?
let info: Info?
}
struct Info: Codable {
let seed: String?
let results, page: Int?
let version: String?
}
struct Result: Codable {
let gender: Gender?
let name: Name?
let location: Location?
let email: String?
let login: Login?
let dob, registered: Dob?
let phone, cell: String?
let id: ID?
let picture: Picture?
let nat: String?
}
struct Dob: Codable {
let date: Date?
let age: Int?
}
enum Gender: String, Codable {
case female = "female"
case male = "male"
}
struct ID: Codable {
let name: String?
let value: String?
}
struct Location: Codable {
let street, city, state: String?
let postcode: Postcode?
let coordinates: Coordinates?
let timezone: Timezone?
}
struct Coordinates: Codable {
let latitude, longitude: String?
}
enum Postcode: Codable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Postcode.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Postcode"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
struct Timezone: Codable {
let offset, description: String?
}
struct Login: Codable {
let uuid, username, password, salt: String?
let md5, sha1, sha256: String?
}
struct Name: Codable {
let title: Title?
let first, last: String?
}
enum Title: String, Codable {
case madame = "madame"
case mademoiselle = "mademoiselle"
case miss = "miss"
case monsieur = "monsieur"
case mr = "mr"
case mrs = "mrs"
case ms = "ms"
}
struct Picture: Codable {
let large, medium, thumbnail: String?
}
The main issue is a type mismatch.
The root object of the JSON is not the People array, it's the umbrella struct, I named it Response
Please change the structs to
struct Response: Decodable {
let results: [Person]
let info: Info
}
struct Info: Decodable {
let seed: String
let results, page: Int
let version: String
}
struct Person: Decodable {
let gender: Gender
let name: Name
let location: Location
let email: String
let login: Login
let dob, registered: Dob
let phone, cell: String
let id: ID
let picture: Picture
let nat: String
}
struct Dob: Decodable {
let date: Date
let age: Int
}
enum Gender: String, Decodable {
case female, male
}
struct ID: Codable {
let name: String
let value: String?
}
struct Location: Decodable {
let street, city, state: String
let postcode: Postcode
let coordinates: Coordinates
let timezone: Timezone
}
struct Coordinates: Codable {
let latitude, longitude: String
}
enum Postcode: Codable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Postcode.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Postcode"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
struct Timezone: Codable {
let offset, description: String
}
struct Login: Codable {
let uuid, username, password, salt: String
let md5, sha1, sha256: String
}
struct Name: Codable {
let title: Title
let first, last: String
}
enum Title: String, Codable {
case madame, mademoiselle, miss, monsieur, mr, mrs, ms
}
struct Picture: Codable {
let large, medium, thumbnail: String
}
Almost all properties can be declared non-optional, the date key in Dob is an ISO8601 date string you have to add the appropriate date decoding strategy. The People array is the property results of the root object.
var dataRoutine = [Person]()
func downloadJsonData(completed : #escaping ()->()){
guard let url = URL(string: "https://randomuser.me/api/?results=100")else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
let statuscode = httpResponse.statusCode
if statuscode == 404{
print( "Sorry! No Routine Found")
}else{
if error == nil{
do{
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let jsonResponse = try decoder.decode(Response.self, from: data!)
self.dataRoutine = jsonResponse.results
DispatchQueue.main.async {
completed()
print(self.dataRoutine.count) // I don't know why my result is ever 1.
}
}catch{
print(error)
}
}
}
}
}.resume()
}

debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)

I want to load an online json file into my application, but I am running into this error:
typeMismatch(Swift.Array,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Array but found a dictionary instead.",
underlyingError: nil))
I have looked on stackoverflow but other sollutions didn't help to solve mine.
My JSON:
{
"copyright" : "NHL and the NHL Shield are registered trademarks of the National Hockey League. NHL and NHL team marks are the property of the NHL and its teams. © NHL 2022. All Rights Reserved.",
"totalItems" : 0,
"totalEvents" : 0,
"totalGames" : 0,
"totalMatches" : 0,
"metaData" : {
"timeStamp" : "20220813_172145"
},
"wait" : 10,
"dates" : [ ]
}
My datamodel:
import Foundation
struct Initial: Codable {
let copyright: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let wait: Int
let dates: [Dates]
}
struct Dates: Codable {
let date: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let games: [Game]
}
struct Game: Codable {
let gamePk: Int
let link: String
let gameType: String
let season: String
let gameDate: String
let status: Status
let teams: Team
let venue: Venue
let content: Content
}
struct Status: Codable {
let abstractGameState: String
let codedGameState: Int
let detailedState: String
let statusCode: Int
let startTimeTBD: Bool
}
struct Team: Codable {
let away: Away
let home: Home
}
struct Away: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct Home: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct LeagueRecord: Codable {
let wins: Int
let losses: Int
let type: String
}
struct TeamInfo: Codable {
let id: Int
let name: String
let link: String
}
struct Venue: Codable {
let name: String
let link: String
}
struct Content: Codable {
let link: String
}
and here is my viewcontroller
import UIKit
class TodaysGamesTableViewController: UITableViewController {
var todaysGamesURL: URL = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule")!
var gameData: [Dates] = []
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
loadTodaysGames()
}
func loadTodaysGames(){
print("load Games")
view.addSubview(activityIndicator)
activityIndicator.frame = view.bounds
activityIndicator.startAnimating()
let todaysGamesDatatask = URLSession.shared.dataTask(with: todaysGamesURL, completionHandler: dataLoaded)
todaysGamesDatatask.resume()
}
func dataLoaded(data:Data?,response:URLResponse?,error:Error?){
if let detailData = data{
print("detaildata", detailData)
let decoder = JSONDecoder()
do {
let jsondata = try decoder.decode([Dates].self, from: detailData)
gameData = jsondata //Hier .instantie wil doen krijg ik ook een error
DispatchQueue.main.async{
self.tableView.reloadData()
}
}catch let error{
print(error)
}
}else{
print(error!)
}
}
Please learn to understand the decoding error messages, they are very descriptive.
The error says you are going to decode an array but the actual object is a dictionary (the target struct).
First take a look at the beginning of the JSON
{
"copyright" : "NHL and the NHL Shield are registered trademarks of the National Hockey League. NHL and NHL team marks are the property of the NHL and its teams. © NHL 2018. All Rights Reserved.",
"totalItems" : 2,
"totalEvents" : 0,
"totalGames" : 2,
"totalMatches" : 0,
"wait" : 10,
"dates" : [ {
"date" : "2018-05-04",
It starts with a { which is a dictionary (an array is [) but you want to decode an array ([Dates]), that's the type mismatch the error message is referring to.
But this is only half the solution. After changing the line to try decoder.decode(Dates.self you will get another error that there is no value for key copyright.
Look again at the JSON and compare the keys with the struct members. The struct whose members match the JSON keys is Initial and you have to get the dates array to populate gameData.
let jsondata = try decoder.decode(Initial.self, from: detailData)
gameData = jsondata.dates
The JSON is represented by your Initial struct, not an array of Dates.
Change:
let jsondata = try decoder.decode([Dates].self, from: detailData)
to:
let jsondata = try decoder.decode(Initial.self, from: detailData)
Correct Answer is done previously from my two friends
but you have to do it better i will provide solution for you to make code more clean and will give you array of Dates
here is your model with codable
import Foundation
struct Initial: Codable {
let copyright: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let wait: Int
let dates: [Dates]
}
struct Dates: Codable {
let date: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let games: [Game]
}
struct Game: Codable {
let gamePk: Int
let link: String
let gameType: String
let season: String
let gameDate: String
let status: Status
let teams: Team
let venue: Venue
let content: Content
}
struct Status: Codable {
let abstractGameState: String
let codedGameState: Int
let detailedState: String
let statusCode: Int
let startTimeTBD: Bool
}
struct Team: Codable {
let away: Away
let home: Home
}
struct Away: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct Home: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct LeagueRecord: Codable {
let wins: Int
let losses: Int
let type: String
}
struct TeamInfo: Codable {
let id: Int
let name: String
let link: String
}
struct Venue: Codable {
let name: String
let link: String
}
struct Content: Codable {
let link: String
}
// MARK: Convenience initializers
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And Here is Controller Will make solution more easy
class ViewController: UIViewController {
var todaysGamesURL: URL = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule")!
var gameData: Initial?
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
self.loadTodaysGames()
}
func loadTodaysGames(){
print("load Games")
let todaysGamesDatatask = URLSession.shared.dataTask(with: todaysGamesURL, completionHandler: dataLoaded)
todaysGamesDatatask.resume()
}
func dataLoaded(data:Data?,response:URLResponse?,error:Error?){
if let detailData = data {
if let inital = try? Initial.init(data: detailData){
print(inital.dates)
}else{
// print("Initial")
}
}else{
print(error!)
}
}
}

JSON decodable swift 4

I'm having a terrible time creating a public struct for the following JSON. I'm trying to extract the temp and humidity. I've tried to to extract the following JSON but I think I'm having a problem with way I'm identifying each of the variables.
Here's the JSON:
{"coord":{"lon":-82.26,"lat":27.76},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"base":"stations","main":{"temp":66.24,"pressure":1021,"humidity":63,"temp_min":62.6,"temp_max":69.8},"visibility":16093,"wind":{"speed":8.05,"deg":80},"clouds":{"all":90},"dt":1523500500,"sys":{"type":1,"id":726,"message":0.0051,"country":"US","sunrise":1523531237,"sunset":1523577156},"id":420006939,"name":"Brandon","cod":200}
And here's the struct
public struct Page: Decodable {
private enum CodingKeys : String, CodingKey {
case coord = "coord", weather = "weather", mainz = "main", visibility = "visibility", wind = "wind", clouds = "clouds", dt = "dt", sys = "sys", id = "id", name = "name", cod = "cod"}
let coord: String
let weather: [String]
let mainz: String
let visibility: Int
let wind: String
let clouds: String
let dt: Int
let sys: String
let id: Int
let name: String
let cod: Int
public struct Main: Decodable {
private enum CodingKeys : String, CodingKey {
case temp = "temp", pressure = "pressure",humidity = "humidity", temp_min = "temp_min", temp_max = "temp_max"
}
let temp: Int
let pressure: Int
let humidity: Int
let temp_min: Int
let temp_max: Int
}
...and the code I'm using the decode...
// Make the POST call and handle it in a completion handler
let task = session.dataTask(with: components.url!, completionHandler: {(data, response, error) in
guard let data = data else { return }
do
{
let result = try JSONDecoder().decode(Page.self, from: data)
for main in result.mainz {
print(main.humidity)
}
}catch
{print("Figure it out")
}
})
task.resume()
Create structures to capture whatever you want. For example, if you're interested in the temperatures and the coordinates, create structures for those:
struct WeatherTemperature: Codable {
let temp: Double
let pressure: Double
let humidity: Double
let tempMin: Double
let tempMax: Double
}
struct WeatherCoordinate: Codable {
let latitude: Double
let longitude: Double
enum CodingKeys: String, CodingKey {
case latitude = "lat"
case longitude = "lon"
}
}
Then create a structure for the whole response:
struct ResponseObject: Codable {
let coord: WeatherCoordinate
let main: WeatherTemperature
}
Note, I only create properties for those keys I care about.
And then decode it:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let responseObject = try decoder.decode(ResponseObject.self, from: data)
print(responseObject.coord)
print(responseObject.main)
} catch {
print(error)
}
That resulted in:
WeatherCoordinate #1(latitude: 27.760000000000002, longitude: -82.260000000000005)
WeatherTemperature #1(temp: 66.239999999999995, pressure: 1021.0, humidity: 63.0, tempMin: 62.600000000000001, tempMax: 69.799999999999997)
Clearly, add whatever other structures/properties you care about, but hopefully this illustrates the idea.
Note, I'd rather not let the JSON dictate my property names, so, for example, I specified a key decoding strategy for my decoder to convert from JSON snake_case to Swift camelCase.
I also like longer property names, so where I wanted more expressive names (latitude and longitude rather than lat and lon), I defined CodingKeys for those. But do whatever you want on this score.