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
}
Related
When I try to present the results I received this message "Response could not be decoded because of error:
The data couldn’t be read because it isn’t in the correct format."
This is my format and its right I think.
import Foundation
// MARK: - Response
struct Response: 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
}
also I try with this code to view the result.
private func fetchData() {
self.task = AF.request(self.baseUrl, method: .get, parameters: nil)
.publishDecodable(type: [Response].self)
.sink(receiveCompletion: {(completion) in
switch completion {
case .finished:
()
case .failure(let error):
print(String(describing: error))
//print(error.localizedDescription)
}
}, receiveValue: {[weak self ](response) in
switch response.result {
case .success(let model): self?.presenters = model.map {PostPresenter(with: $0)}
case.failure(let error):
print(String(describing: error))
// print(error.localizedDescription)
}
})
}
And my post presenter code is this
struct PostPresenter: Identifiable {
let id = UUID()
let title: String
init(with model:Response) {
self.title = model.data
}
}
Two mistakes
The root object is a dictionary so it's (type: Response.self)
and model.data is [Datum] so declare
struct PostPresenter: Identifiable {
let id = UUID()
let data: [Datum]
init(with response: Response) {
self.data = response.data
}
}
And in a Codable context print the error always print(error), not print(String(describing: error)) and never print(error.localizedDescription)
My right Post presenter
struct PostPresenter: Identifiable {
let id = UUID()
let data: [Datum]
init(with response: Response<[Datum]>) {
self.data = response.data
}
init() {
data = []
}
}
And my finally format as I right.
struct Response<T: Decodable>: Decodable {
let code: Int
let meta: Meta
let data: T
}
// MARK: - Datum
struct Datum: Decodable {
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: Decodable {
let pagination: Pagination
}
// MARK: - Pagination
struct Pagination: Decodable {
let total, pages, page, limit: Int
}
And finally my homeview and how I present
struct HomeView: View {
#ObservedObject var viewModel = HomeAPIViewModel()
var body: some View {
ZStack {
Color(.white)
// Present the API Here
List(self.viewModel.presenter.data, id: \.id) {
Text($0.title)
}
.padding()
}
}
}
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 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
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!)
}
}
}
I want to create variable with type of Codable. And later to use it in the JSONEncoder class. I thought that code from below should work fine, but it gives me error:
Cannot invoke encode with an argument list of type (Codable).
How to declare codable variable that JSONEncoder will be taking without error?
struct Me: Codable {
let id: Int
let name: String
}
var codable: Codable? // It must be generic type, but not Me.
codable = Me(id: 1, name: "Kobra")
let data = try? JSONEncoder().encode(codable!)
Here is similar question how to pass Codable using function. But I am looking how to set Codable using variable (class variable).
Your code is all right, the only thing we need to focus is Codable.
Codable is a typealias which won't give you generic type.
JSONEncoder().encode(Generic confirming to Encodable).
So, i modified the code as below, it may help you..
protocol Codability: Codable {}
extension Codability {
typealias T = Self
func encode() -> Data? {
return try? JSONEncoder().encode(self)
}
static func decode(data: Data) -> T? {
return try? JSONDecoder().decode(T.self, from: data)
}
}
struct Me: Codability
{
let id: Int
let name: String
}
struct You: Codability
{
let id: Int
let name: String
}
class ViewController: UIViewController
{
override func viewDidLoad()
{
var codable: Codability
codable = Me(id: 1, name: "Kobra")
let data1 = codable.encode()
codable = You(id: 2, name: "Kobra")
let data2 = codable.encode()
}
}
I created the same scenario as yours:
struct Me: Codable
{
let id: Int
let name: String
}
struct You: Codable
{
let id: Int
let name: String
}
class ViewController: UIViewController
{
override func viewDidLoad()
{
var codable: Codable?
codable = Me(id: 1, name: "Kobra")
let data1 = try? JSONEncoder().encode(codable)
codable = You(id: 2, name: "Kobra")
let data2 = try? JSONEncoder().encode(codable)
}
}
The above code is not giving me any error. The only thing I changed is:
let data = try? JSONEncoder().encode(codable!)
I didn't unwrap codable and it is working fine.
I have used this way, maybe it helps on your case as well.
public protocol AbstractMessage: Codable {
var id: Int { get } // you might add {set} as well
var name: Int { get }
}
then created a method:
public func sendMessage<T>(message: T) where T: AbstractMessage {
let json = try! String(data: JSONEncoder().encode(message), encoding: .utf8)!
...
}
Here I created a common protocol, and passed it as a generic type to my function.