Having trouble decoding JSON Data in swift programming language - ios

I am having trouble decoding JSON Data in swift after using an API call. The application is actually a Pokedex application. I am having trouble; specifically on the line where I initialize the variable flavorTexts. I have found that I have received data from the URL (checkout https://pokeapi.co/) to view the pokemon data. When I print this data it comes out as x number of bytes. But after I decode it into flavorTexts and I print flavorTexts, nothing prints meaning flavorTexts has nothing in it. I have also found that the DispatchQueue function does not even begin as I can't any prints inside it do not print. Can someone help me? The third set of code is the data structs I have created for these API calls. For the API call I am having trouble with the structs used are: descriptions, items, and language.
The error I am getting is:
keyNotFound(CodingKeys(stringValue: "lang", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "flavor_text_entries", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"lang\", intValue: nil) (\"lang\").", underlyingError: nil))
API CALL:
func loadDescription () {
url2 = "https://pokeapi.co/api/v2/pokemon-species/" + (name) + "/"
urlReal = URL(string: url2)
print(url2!)
URLSession.shared.dataTask(with: urlReal) { (data1, response, error) in
guard let data1 = data1 else {
return
}
print(data1)
do {
//This is where I am having trouble
let flavorTexts = try JSONDecoder().decode(descriptions.self, from: data1)
// DispatchQueue.main.async {
// var fte = flavor_text_entry.flavor_text_entries
// for ft in fte {
// if ft.lang.name == "en" {
// self.textDescription.text = ft.flavor_text
// break
// }
// }
// }
}
catch {
error
}
}.resume()
}
Data Structs:
import Foundation
struct PokemonListResults: Codable {
let results: [PokemonListResult]
}
struct PokemonListResult: Codable {
let name: String
let url: String
}
struct PokemonResult: Codable {
let id: Int
let name: String
let types: [PokemonTypeEntry]
}
struct PokemonTypeEntry: Codable {
let slot: Int
let type: PokemonType
}
struct PokemonType: Codable {
let name: String
}
struct PokemonImage: Codable {
let sprites: sprite
}
struct sprite: Codable {
let front_default: String
}
struct descriptions: Codable {
let flavor_text_entries: [items]
}
struct items: Codable {
let flavor_text: String
let lang: language
}
struct language: Codable {
let name: String
}

Related

Xcode - API Failing When Search Size is Greater Than 14

When fetching data from the iTunes API https://itunes.apple.com/search?term=\(search)&entity=software&limit=14, the fetch fails if the limit is a larger number (e.g. 30, 40, 50 etc.). The limit is denoted by limit=14 found at the end of the URL. 14 is the number of results returned. This can be changed to any number.
When making the call in Postman, I can enter the limit as any number and it works without error. Additionally, when running the api with a large number in XCtest, the test passes. It only seems to fail when making the call live in the app.
The failure occurs in the guard let statement. In the code below, if the number is too large (e.g. 50), it prints "failed to fetch data" - indicating that there is a URL issue. When using a smaller number (e.g. 10), the fetch is successful and data returns in my table view. You can also change the search term. Currently I have it set to "Apple".
Below is the code for the API:
import Foundation
import UIKit
struct Response: Codable {
var resultCount: Int
var results: [Result]
}
struct Result: Codable {
var screenshotUrls, ipadScreenshotUrls, appletvScreenshotUrls: [String]
var artworkUrl60, artworkUrl512, artworkUrl100, artistViewUrl: String
var supportedDevices, advisories: [String]
var isGameCenterEnabled: Bool
var features: [String]
var kind, minimumOsVersion, trackCensoredName, fileSizeBytes: String
var contentAdvisoryRating: String
var genreIds: [String]
var primaryGenreName, artistName, trackContentRating, trackName, releaseDate, sellerName, currentVersionReleaseDate, releaseNotes, version: String
var primaryGenreId: Int
var currency, description: String
var price: Double
var averageUserRating: Double
}
class API {
var storedData = Response(resultCount: Int.init(), results: [])
func loadData(search: String, completionHandler: #escaping (Response) -> Void) {
guard let url = URL(string:"https://itunes.apple.com/search?term=\(search)&entity=software&limit=40") else {
print("failed to fetch data")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.storedData.resultCount = response.resultCount
self.storedData.results = response.results
completionHandler(self.storedData)
}
return
}
}
print("failed \(error?.localizedDescription ?? "unknown error")")
}
.resume()
}
func reloadTableData() {
DataManager.shared.viewController.tableView.reloadData()
}
}
Any thoughts as to why a larger number causes the guard let to fail when running the app, but does not fail in my tests or postman?
EDIT
Below is how I am calling the function. I am calling it in viewdidload. It uses a completion handler, so it looks like the following:
api.loadData(search: "ibm") { Results in
self.filteredResults = self.api.storedData.results //stores value in filtered results array
self.tableView.reloadData() //refreshes table view - table view is referencing the filteredResults array
}
There are many keys missing in the JSON, when you set a limit of more than 20:
Always use doCatch when decoding JSON and print the error, which tells you what went wrong, in your source decoding is failing because of the following error: refer
keyNotFound(CodingKeys(stringValue: "releaseNotes", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "results", intValue: nil), _JSONKey(stringValue: "Index 15", intValue: 15)], debugDescription: "No value associated with key CodingKeys(stringValue: \"releaseNotes\", intValue: nil) (\"releaseNotes\").", underlyingError: nil))
Try making all variables in the struct Optional to fix decoding issue for now.
Make properties optional
struct Response: Codable {
var resultCount: Int?
var results: [Result]?
}
struct Result: Codable {
var screenshotUrls, ipadScreenshotUrls, appletvScreenshotUrls: [String]?
var artworkUrl60, artworkUrl512, artworkUrl100, artistViewUrl: String?
var supportedDevices, advisories: [String]?
var isGameCenterEnabled: Bool?
var features: [String]?
var kind, minimumOsVersion, trackCensoredName, fileSizeBytes: String?
var contentAdvisoryRating: String?
var genreIds: [String]?
var primaryGenreName, artistName, trackContentRating, trackName, releaseDate, sellerName, currentVersionReleaseDate, releaseNotes, version: String?
var primaryGenreId: Int?
var currency, description: String?
var price: Double?
var averageUserRating: Double?
}
Put this inside the if-let data block:
do {
let response = try JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
self.storedData.resultCount = response.resultCount
self.storedData.results = response.results
completionHandler(self.storedData)
}
} catch let error {
print(error)
}

Error to parse from alomofire post request

I have an issue with parsing using alamofire. I get an error to try to decode the json file that i get in return from the request.
I have tried to parse JSON file that looks like this:
success({
data = {
id = "eb259a9e-1b71-4df3-9d2a-6aa797a147f6";
nickname = joeDoe;
options = {
avatar = avatar1;
};
rooms = "<null>";
};
})
It Gives me an error that looks like this:
keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil))
The user model looks like this:
import Foundation
struct userModel: Codable {
let id: String
let nickname: String
let options: options
let rooms: String
enum CodingKeys: String, CodingKey {
case id = "id"
case nickname = "nickname"
case options = "options"
case rooms = "rooms"
}
}
struct options: Codable {
var avatar: String?
enum CodingKeys: String, CodingKey {
case avatar = "avatar"
}
}
And the function looks like this:
func postUser(){
AF.request("http://test.com/", method: .post, parameters: user).responseJSON {response in
guard let itemsData = response.data else {
print("test1")
return
}
do {
print("hallo!")
let decoder = JSONDecoder()
print("")
print(itemsData)
print("")
print(response.description)
let items = try decoder.decode(userModel.self, from: itemsData)
print(items)
DispatchQueue.main.async {
print("test2")
}
} catch {
print(error)
print("test3")
}
}
How do i fix the issue?
You are ignoring the root object, the dictionary with key data.
Create another struct
struct Root : Decodable {
let data : UserModel
}
And please name structs with starting capital letter and you don't need the CodingKeys if the struct member names match the keys
struct UserModel: Codable {
let id: String
let nickname: String
let options: Options
let rooms: String
struct Options: Codable {
var avatar: String?
}
Then decode
let result = try decoder.decode(Root.self, from: itemsData)
let items = result.data
Consider that AF can decode JSON with JSONDecoder implicitly.

How to make GET API calls that has large JSON response in Swift 5?

I am trying to fetch the Covid 19 data of all countries and their states from disease.sh.
I have previously fetched json data from different APIs using this method. The response in those cases were shorter compared to this.
I have posted the codes below:
// Webservice.swift
import Foundation
class Webservice {
let countriesURL: String = "https://disease.sh/v3/covid-19/jhucsse"
func getAllCountries(completion: #escaping ([Country]?) ->()) {
guard let url = URL(string: countriesURL) else {
completion(nil)
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("No data in response: \(error?.localizedDescription ?? "Unknown error").")
DispatchQueue.main.async {
completion(nil)
}
return
}
let countries = try? JSONDecoder().decode([Country].self, from: data)
DispatchQueue.main.async {
countries == nil ? completion(nil) : completion(countries)
}
}.resume()
}
}
Since, I was using MVVM design, here are my Model, ViewModel and View.
// Model
// Country.swift
import Foundation
struct Country: Decodable {
var country: String
var updatedAt: String
var stats: Stats
var coordinates: Coordinates
var province: String
}
struct Stats: Decodable {
var confirmed: Int
var deaths: Int
var recovered: Int
}
struct Coordinates: Decodable {
var latitude: String
var longitude: String
}
// ViewModel
// CountryListViewModel.swift
import Foundation
class CountryListViewModel: ObservableObject {
#Published var countries = [CountryViewModel]()
init() {
fetchCountries()
}
func fetchCountries() {
Webservice().getAllCountries() { countries in
if let countries = countries {
self.countries = countries.map(CountryViewModel.init)
}
}
}
}
class CountryViewModel {
var country: Country
init(country: Country) {
self.country = country
}
let id = UUID()
var name: String {
return self.country.country
}
var updatedAt: String {
return self.country.updatedAt
}
var stats: Stats {
return self.country.stats
}
var coordinates: Coordinates {
return self.country.coordinates
}
var province: String {
return self.country.province
}
}
// View
// ContentView.swift
import SwiftUI
struct ContentView: View {
#ObservedObject private var countryListVM = CountryListViewModel()
var body: some View {
List( self.countryListVM.countries, id:\.id) { country in
Text(country.name)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
My issue is when I call the Webservice.getAllCountries() it returns nil. Could anyone look at the code and tell me what is wrong, please? Thank you!
PS: I created a mock json with fewer objects (20-30) and called Webservice.getAllCountries() in this case it returned and mapped the values. It is not working with larger JSON response. Help!!
You should avoid using try ? except in situations where you really don't care about failures. do/try/catch is a better approach since it will tell you why something failed.
Changing your code to
do {
let countries = try JSONDecoder().decode([Country].self, from: data)
DispatchQueue.main.async {
completion(countries)
}
} catch {
print(error)
completion(nil)
}
Gives us an error on the console -
Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 509", intValue: 509), CodingKeys(stringValue: "province", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
which makes sense, since not all countries have provinces. To fix this, make province an optional in your data model
struct Country: Decodable {
var country: String
var updatedAt: String
var stats: Stats
var coordinates: Coordinates
var province: String?
}

How to use Alamofire with Codable protocol and parse data

Firs time using Codable protocol so many things not clear. After going through several tutorials started with the Codable thing to parse data. Below is the code:
struct MyTasks : Codable {
var strDateDay : String = ""
var strReminder : String = ""
var strRepetitions : String = ""
var name : String = ""
var strNotes : String = ""
var strUserId : String = ""
private enum CodingKeys: String, CodingKey {
case strDateDay = "duedate"
case strReminder = "reminder"
case strRepetitions = "recurring"
case name = "name"
case strNotes = "notes"
case strUserId = "userId"
}
}
let networkManager = DataManager()
networkManager.postLogin(urlString: kGetMyTasks, header: header, jsonString: parameters as [String : AnyObject]) { (getMyTasks) in
print(getMyTasks?.name as Any) // -> for log
Common_Methods.hideHUD(view: self.view)
}
// Network manager:
func postLogin(urlString: String, header: HTTPHeaders, jsonString:[String: AnyObject], completion: #escaping (MyTasks?) -> Void) {
let apiString = KbaseURl + (kGetMyTasks as String)
print(apiString)
Alamofire.request(apiString, method: .post, parameters: jsonString , encoding: URLEncoding.default, headers:header).responseJSON
{ response in
let topVC = UIApplication.shared.keyWindow?.rootViewController
DispatchQueue.main.async {
//Common_Methods.showHUD(view: (topVC?.view)!)
}
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
let loginRequest = try decoder.decode(MyTasks.self, from: data)
completion(loginRequest)
} catch let error {
print(error)
completion(nil)
}
}
}
Now, this is the response:
keyNotFound(CodingKeys(stringValue: "strDateDay", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"strDateDay\", intValue: nil) (\"strDateDay\").", underlyingError: nil))
Below is the json response i am trying to parse:
{
"data": [
{
"userId": 126,
"name": "My task from postman ",
"notes": null,
"totalSteps": 0,
"completedSteps": 0,
"files": 0
},
{
"userId": 126,
"name": "My task from postman 1",
"notes": null,
"totalSteps": 0,
"completedSteps": 0,
"files": 0
}
]
}
I know i am missing something but even after spending more than half day i haven't properly understood what is wrong and where. Please guide.
problem is with your struct the names of the properties in the struct should be match with json data or if you want to use custom name you should use enum CodingKeys to convert them to your liking
struct MyTasks: Codable {
let data: [Datum]
}
struct Datum: Codable {
let userID: Int?
let name: String
let notes: String?
let totalSteps, completedSteps, files: Int
enum CodingKeys: String, CodingKey {
case userID = "userId"
case name, notes, totalSteps, completedSteps, files
}
}
func postLogin(urlString: String, header: HTTPHeaders, jsonString:[String: AnyObject], completion: #escaping (MyTasks?) -> Void) {
let apiString = KbaseURl + (kGetMyTasks as String)
print(apiString)
Alamofire.request(apiString, method: .post, parameters: jsonString , encoding: URLEncoding.default, headers:header).responseJSON
{ response in
let topVC = UIApplication.shared.keyWindow?.rootViewController
DispatchQueue.main.async {
//Common_Methods.showHUD(view: (topVC?.view)!)
}
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
let loginRequest = try decoder.decode(MyTasks.self, from: data)
completion(loginRequest)
} catch let error {
print(error)
completion(nil)
}
}
}
And one more thing keep in mind that you know the exact type of notes and make it optional otherwise it rise error, there was no type so I put a optional String in there.
Hope this will help.
The problem is that in your top JSON you have a single "data" property of type array.
You are asking JSONDecoder to decode a JSON object containing only "data" property into a Swift object called "MyTasks" with the stored properties you defined (including strDateDay).
So the decoder sends you this response because he can't find the strDateDay in that top object.
You have to make a top object for deserialization. Something like :
struct MyResponse: Codable {
var data: [MyTasks]
}
Then just give it to your JSONDecoder like you have already done :
let loginRequest = try decoder.decode(MyResponse.self, from: data)
The data you send to the decoder is your full JSON stream (the data property of Alamofire's response object), and not only the "data" property of your JSON structure.
I'd suggest to use CodyFire lib cause it support Codable for everything related to requests.
Your POST request with it may look like
struct MyTasksPayload: JSONPayload {
let param1: String
let param2: String
}
struct MyTasksResponseModel: Codable {
let strDateDay: String
let strReminder: String
let strRepetitions: String
let name: String
}
let server = ServerURL(base: "https://server1.com", path: "v1")
APIRequest<[MyTasksResponseModel]>(server, "mytasks", payload: payloadModel)
.method(.post)
.onRequestStarted {
// show HUD here
}
.onError {
// hide hud and show error here
}
.onSuccess { tasks in
// here's your decoded tasks
// hide hud here
}
Use APIRequest<[MyTasksResponseModel]> to decode array
Use APIRequest to decode one object

parse JSON, iOS, Swift4

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)
}

Resources