I am new to Swift and am having a spot of bother with a decodable class.
I am receiving an error on this class : Type 'VoucherCode' does not conform to protocol 'Decodable'
I have exactly the same syntax in another project without error which came from a tutorial.
Without the published line, the class works and is decoded from relevant json.
What am I missing please?
import Foundation
class VoucherCode: Decodable, Identifiable, ObservableObject {
#Published var logoData: Data?
var id:UUID?
var title: String?
var voucherCode: String?
var details: String?
var logo: String?
var url: String?
var termsAndConditions: String?
var highlight: String?
var whoFor:[String]?
func getLogoData() {
guard logo != nil else {
return
}
if let url = URL(string: logo!) {
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { (data, response, error) in
if error == nil {
DispatchQueue.main.async {
self.logoData = data!
}
}
}
dataTask.resume()
}
}
}
A similar class (which works) from a CodeWithChris lesson. There is no error and it works.
add this to your class:
private enum CodingKeys: String, CodingKey {
case id, title, voucherCode, details, logo, url, termsAndConditions, highlight, whoFor
}
this will exclude logoData from the decodable and make VoucherCode Decodable.
Related
My Firestore data is set up like this:
This is how I'm reading the data:
for doc in snapshot!.documents {
let recipeFromFirestore = Recipe(
glutenFree: doc["glutenFree"] as! Bool,
dairyFree: doc["dairyFree"] as! Bool,
cheap: doc["cheap"] as! Bool)
recipes.append(recipeFromFirestore)
}
These are my Recipe and ExtendedIngredients structs:
struct Recipe: Codable {
var glutenFree: Bool?
var dairyFree: Bool?
var cheap: Bool?
var extendedIngredients: [ExtendedIngredients]? = nil
}
struct ExtendedIngredients: Codable {
var aisle: String?
var image: String?
var name: String?
var amount: Double?
var unit: String?
}
How can I go about reading the array of Map type data in my extendedIngredients field in Firestore? I'm not sure how to include that in my let recipeFromFirestore code.
Any help or guidance is much appreciated!
I was able to get all of my data, including the Map type by using the Codable API.
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.recipe = try document.data(as: Recipe.self)
let recipeFromFirestore = Recipe(
glutenFree: self.recipe!.glutenFree,
dairyFree: self.recipe!.dairyFree,
cheap: self.recipe!.cheap,
extendedIngredients: self.recipe!.extendedIngredients)
self.recipes.append(recipeFromFirestore)
}
catch {
print("Line 136: \(error)")
}
}
}
}
I did not need to do any explicit mapping with this approach.
I'm trying to make a stocks app for college related work and ive done almost everything i need to do except getting the actual data of the stocks into my app, I've been trying this and researching for the past couple days but still cannot get it to work as i get the error message :
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
any help is appreciated as this is the first ever thing i've made with swift/xcode
the code in my viewController:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchPostData { (posts) in
for post in posts {
print(post.datetime!)
}
}
}
func fetchPostData(completionHandler: #escaping ([Post]) -> Void) {
let url = URL(string: "https://api.twelvedata.com/time_series?symbol=AAPL&interval=1min&apikey=<api-key>")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let postsData = try JSONDecoder().decode([Post].self, from: data)
completionHandler(postsData)
}
catch {
let error = error
print(error)
}
}.resume()
}
}
and the other file with the variables:
struct Post: Codable {
var datetime: Int!
var open: Int!
var high: String!
var low: String!
var close: String!
var volume: String!
}
link to the json file: https://api.twelvedata.com/time_series?symbol=AAPL&interval=1min&apikey=0561a81a9baf4ae4bc65c7af9196f929
The error is speaking for itself; you are trying to decode an array but the JSON is a dictionary. You need to change your decode:
JSONDecoder().decode(Post.self, from: data)
Edit after Joakim Danielson hint for completeness of the answer:
You have also to modify your struct in order to accomodate the JSON in your response
struct Post: Decodable {
let meta: Meta
}
struct Meta: Decodable {
let symbol: String
let interval: String
let currency: String
let values: [Values]
// more data here
}
struct Values: Decodable {
// more data here
}
First of all, all values in Post are String, please note the double quotes in the JSON
struct Post: Decodable {
let datetime, open, high, low, close, volume: String
}
But the main issue is – as mentioned in the comments and Alastar's answer and indirectly stated by the error – you are ignoring the root object. You have to decode JSON always from the top. Add this struct
struct Root: Decodable {
let status: String
let values: [Post]
}
and decode
let postsData = try JSONDecoder().decode(Root.self, from: data)
completionHandler(postsData.values)
This is my first-time question.
I thought I did everything correctly. Have I built my model wrong? I'm not sure how to fix this error.
This is my code, after the correct model I want to pass the data to the table:
func createJSON() {
if let url = URL(string: "https://newsapi.org/v2/everything?q=apple&from=2020-11-15&to=2020-11-15&sortBy=popularity&apiKey=c5722efe6e65432fb5c116d3e1403dca") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else { return }
var result: NewsResult?
do {
result = try JSONDecoder().decode(NewsResult.self, from: data)
} catch {
print("error masage: \(error)")
}
guard let finalResult = result else { return }
print(finalResult.status)
print(finalResult.totalResults)
// print(finalResult.articles)
// let newNews = finalResult.artiscles
// self.newsApple.append(contentsOf: newNews)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
task.resume()
}
}
// MARK: - Model Data Source
struct NewsResult:Codable {
let status: String
let totalResults: Int // totalResults
let articles: [articles]
}
struct articles:Codable {
let author: String // articles[0].author
let title: String
let description: String
let url:String
let urlToImage:String
let publishedAt: String
let content: String
}
You have written the model code that was not mapping correctly with JSON resultant. Try replacing your model structure with the following code:
struct NewsResult: Codable {
var status: String?
var totalResults: Int?
var articles: [Article]?
}
struct Article: Codable {
var source: Source?
var author: String?
var title: String?
var articleDescription: String?
var url: String?
var urlToImage: String?
var publishedAt: String?
var content: String?
}
struct Source: Codable {
var id : String?
var name: String?
}
I have the Url saved in the info.plist as such:
BASE_URL <-> String <-> $(BASE_URL)
and in my project's Build Settings, I added a user-defined setting as such:
BASE_URL http://data.nba.net
After setting this up, when I try to get the website into the url variable, the variable returns "". As I debug the issue, I don't see the website stored under that variable.
I am new to Swift and still learning so any comments on the way I have setup my structs will be appreciated as well.
import UIKit
struct sports_content: Decodable {
let sports_meta_expanded: sports_meta
let teams_expanded: teams
}
struct sports_meta: Decodable {
let date_time: String?
let season_meta_list: season_meta
}
struct season_meta: Decodable {
let calendar_date: Date
let season_year: Int?
let stats_season_year: Int?
let stats_season_id: Int?
let stats_season_stage: Int?
let roster_season_year: Int?
let schedule_season_year: Int?
let standings_season_year: Int?
let season_id: Int?
let display_year: String
let display_season: String
let season_stage: Int?
}
struct next: Decodable {
let url: String
}
struct teams: Decodable {
let season_year: year
let team_data: [team]
}
struct year: Decodable {
let season_year: Int?
}
struct team: Decodable {
let is_nba_team: Bool
let team_name: String
let team_nickname: String
let team_code: String
let team_abbrev: String
let city: String
let state: String
let team_short_name: String
let team_id: Int?
let conference: String
let division_id: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.infoDictionary?["BASE_URL"] as? String ?? ""
guard let convertedURL = URL(string: url) else {
return
}
URLSession.shared.dataTask(with: convertedURL) { (data, response, error) in
guard let data = data else {
return
}
do{
let dataSet = try JSONDecoder().decode(sports_content.self, from: data)
print(dataSet)
} catch {
print("JSONSerialization error:", error)
}
}.resume()
}
}
A build setting is used at build / compile time and not necessarily at run time.
To get your URL into the infoDictionary, you need to add it to the Info.plist file. Double click on your Info.plist to get the view open in your Xcode, then click "Add Value" under the Editor menu, then you can add BASE_URL as the key and your URL as the value.
Try using $(BASE_URL) as the value in your Info.plist and see if your build setting gets added in at build time. :)
I am trying to parse some json but seem to be getting nil in the outputs.
I am not sure where I am going wrong and could use some help trying to figure this out.
struct albumInfo: Decodable {
var name: String?
var artist: String?
var url: String?
var playcount: String?
var listeners: String?
var releasedate: String?
var summary: String?
}
class SearchVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Choice = "album"
Album = "Believe"
Artist = "Cher"
let tryURL = "\(BASE_URL)\(Choice!).getinfo&api_key=\(API_KEY)&artist=\(Artist!)&album=\(Album!)&format=json"
print(tryURL)
guard let url = URL(string: tryURL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let albumDescription = try JSONDecoder().decode(albumInfo.self, from: data)
print(albumDescription.artist)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}
}.resume()
}
Here is the data as shown with the tryUrl.
First of all please conform to the naming convention that struct names start with a capital letter.
There are two major issues:
The root object is a dictionary with one key album containing the dictionary with keys name, listeners etc.
The key summary is in another dictionary for key wiki.
The structure of the JSON is very easy to identify. The body within each pair of braces ({}) represents a separate struct.
Further there is no key releasedate so this struct member has to be declared as optional, all other members can be declared as non-optional and as constants (let). url can be declared as URL for free.
Change your structs to
struct Root : Decodable {
let album : AlbumInfo
}
struct AlbumInfo: Decodable {
let name: String
let artist: String
let url: URL
let playcount: String
let listeners: String
let releasedate: String?
let wiki : Wiki
}
struct Wiki: Decodable {
let content: String
let published: String
let summary: String
}
and decode Root
let albumDescription = try JSONDecoder().decode(Root.self, from: data)
print(albumDescription.album.artist)
The first key of your response is "album", you need to parse that first.
The classes do not correspond to json, I guess you should use the following approach (new classes implement your decode, encode protocol):
class JsonInfo {
var album : albumInfo
}
do {
let albumDescription = try JSONDecoder().decode(albumInfo.self, from: data)
print(albumDescription.album.artist)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}