Getting Nil trying to decode json - swift

I'm trying to decode the Json from this URL(:
https://api.letsbuildthatapp.com/appstore/featured
from "Lets build that app tutorials"
This is the code I wrote:
import UIKit
class AppCategory: NSObject{
var name: String?
var apps: [App]?
static func fetchedFeaturedApps(){
let jsonUrlString = "https://api.letsbuildthatapp.com/appstore/featured"
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 featured = try JSONDecoder().decode(Featured.self, from: data)
}catch{
print(err)
}
}.resume()
}
}
struct Featured: Decodable {
var bannerCategory: Banner?
var featuredCategories: [mCategory]?
}
struct Banner: Decodable {
var name: String?
var apps: [String]?
var type: String?
}
struct mCategory: Decodable {
var name: String?
var apps: [App]?
var type: String?
}
struct App: Decodable {
var id: Int?
var name: String?
var category: String?
var price: Float?
var imageName: String?
}
I tried to follow the tutorial but It didn't work for me.
I always get nil while trying to decode the json from the url. I'm really new to this and can't figure out what I'm doing wrong. I know to decode the json properly I need to have Structs as same as the json (like an app array in category) but it still won't work.
EDIT: Should've mentioned, When I run it, the code goes into the catch block and printing "nil".
I tried printing the 'err' but this is all I get in the log:
2019-02-09 19:07:45.241000+0200 AppStore[2344:120273] [AXMediaCommon] Unable to look up screen scale
2019-02-09 19:07:45.241153+0200 AppStore[2344:120273] [AXMediaCommon] Unexpected physical screen orientation
2019-02-09 19:07:45.314112+0200 AppStore[2344:120273] [AXMediaCommon] Unable to look up screen scale
2019-02-09 19:07:45.319977+0200 AppStore[2344:120273] [AXMediaCommon] Unable to look up screen scale
2019-02-09 19:07:45.320189+0200 AppStore[2344:120273] [AXMediaCommon] Unexpected physical screen orientation
nil

You have
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "bannerCategory", intValue: nil)], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))
you need
struct Featured: Codable {
let bannerCategory: BannerCategory
let categories: [Category]
}
struct BannerCategory: Codable {
let name: String
let apps: [BannerCategoryApp]
let type: String
}
struct BannerCategoryApp: Codable {
let imageName: String
enum CodingKeys: String, CodingKey {
case imageName = "ImageName"
}
}
struct Category: Codable {
let name: String
let apps: [CategoryApp]
let type: String
}
struct CategoryApp: Codable {
let id: Int?
let name, category: String?
let price: Double?
let imageName: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
case category = "Category"
case price = "Price"
case imageName = "ImageName"
}
}
class AppCategory: NSObject{
var name: String?
var apps: [CategoryApp]?
static func fetchedFeaturedApps(){
let jsonUrlString = "https://api.letsbuildthatapp.com/appstore/featured"
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 featured = try JSONDecoder().decode(Featured.self, from: data)
print(featured)
}catch{
print(error)
}
}.resume()
}
}

Related

URLSession's Data Task returning Nil when I try to conform the object to Identifiable protocol

I'm trying to run a simple http request to Spotify's api to get the recent played tracks
"https://api.spotify.com/v1/me/player/recently-played?limit=2&before=1676369852305"
Everything works and I do get the result back, but when I add the Identifiable protocol with id variable to my struct objects the decoder fails (nil).
(I want to conform to Identifiable because I need to use the data in a List/ForEach loop
Here's my code
//objects
struct Recent: Codable { //or Codable, Identifiable
//let id: UUID?
let items: [Item]?
let next: String?
let limit: Int?
let href: String?
}
struct Item: Codable { //or Codable, Identifiable
// let id: UUID?
let track: Track?
let played_at: String?
}
struct Track: Codable { //or Codable, Identifiable
//let id: String?
let name: String?
let popularity: Int?
let trackNumber: Int?
let type, uri: String?
}
//function
func getRecentPlayed(miliTime: String) {
//let miliTime = "1676369852305"
let urlString = "https://api.spotify.com/v1/me/player/recently-played?limit=10&before=\(miliTime)"
let url = URL(string: urlString)
let urlSessionConfig = URLSessionConfiguration.default
urlSessionConfig.httpAdditionalHeaders = ["Authorization": "Bearer \(OAuthToken)"]
let urlSession = URLSession(configuration: urlSessionConfig)
let sessionTask = urlSession.dataTask(with: url!) { data, response, error in
if (error == nil && data != nil) {
let jsondecoder = JSONDecoder()
let recentPlayedData = try? jsondecoder.decode(Recent.self, from: data!)
print (recentPlayedData)
recent = recentPlayedData
} else {
print (error)
}
}
sessionTask.resume()
}
What I want:
Run the following loop in swiftui but it screams saying
"Referencing initializer on 'ForEach' requires that 'Item' conform to 'Identifiable"
//swiftui
#State var recent: Recent?
if let r = recent {
NavigationView {
List {
ForEach(r.items!) { item in
Text((item.track?.name)!)
}
}
.navigationTitle("Recently played")
}
}
Any idea anyone?
You can use what you have that is already unique
struct Recent: Codable {
var id: String?{
href
}
let items: [Item]?
let next: String?
let limit: Int?
let href: String?
}
struct Item: Codable, Identifiable {
var id:String?{
track?.id
}
let track: Track?
let played_at: String?
}
struct Track: Codable, Identifiable { //or Codable, Identifiable
var id: String?{
uri
}
let name: String?
let popularity: Int?
let trackNumber: Int?
let type, uri: String?
}
It is not possible exactly as you want, I think that the closest thing to what you want is this:
struct Track: Codable, Identifiable{ //or
let id = UUID() //or something else, it is not clear what is your id
let name: String?
let popularity: Int?
let trackNumber: Int?
let type, uri: String?
}

Swiift - Decoding json data from server

Im working on a project where I have to display data from a network call. The problem is Im having trouble decoding data the data that I received from the network call and storing it into structs variable to use for other calls. The deadline is coming close and Im not sure why my code does not work. This is json that I receive back
{"result":{"login":{"isAuthorized":true,"isEmpty":false,"userName":{"isEmpty":false,"name":{"firstName":"Jason","lastName":"Test","displayName":"Test, Jason","isEmpty":false,"fullName":"Jason Test"},"canDelete":false,"id":5793,"canModify":false},"username":"test#testable.com"},"parameters":{"isEmpty":false,"keep_logged_in_indicator":false,"username":"test#testable.com"}},"isAuthorized":true,"version":{"major":"2021","minor":"004","fix":"04","display":"2021.004.04","isEmpty":false},"isSystemDown":false,"timestamp":"2021-07-26T20:21:43Z","isSuccess":true}
This is the different struct that I made in my project
struct ApiResponse: Decodable {
let results: Results?
let isAuthorized: Bool?
let version: Version?
let isSystemDown: Bool?
let errors: [serverError]?
let timestamp: Date?
let isSuccess: Bool?
}
// MARK: - Result
struct Results: Decodable {
let login: Login
let parameters: Parameters?
}
// MARK: - Login
struct Login: Decodable {
let isAuthorized, isEmpty: Bool?
let userName: UserName
let username: String?
}
// MARK: - UserName
struct UserName: Decodable {
let isEmpty: Bool?
let name: Name
let canDelete: Bool?
let id: Int
let canModify: Bool?
}
// MARK: - Name
struct Name: Decodable {
let firstName, lastName, displayName: String
let isEmpty: Bool?
let fullName: String
}
// MARK: - Parameters
struct Parameters: Decodable {
let isEmpty, keepLoggedInIndicator: Bool?
let username: String?
enum CodingKeys: String, CodingKey {
case isEmpty
case keepLoggedInIndicator
case username
}
}
// MARK: - Version
struct Version: Decodable {
let major, minor, fix, display: String?
let isEmpty: Bool?
}
// Mark: - Error
struct serverError: Decodable {
let password: String?
}
The code that I am using to decode the json data is this
private func handleResponse<T: Decodable>(result: Result<Data, Error>?, completion: (Result<T, Error>) -> Void) {
guard let result = result else {
completion(.failure(AppError.unknownError))
return
}
switch result {
case .success(let data):
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print("Server JsonObject response: \(json)")
} catch {
completion(.failure(error))
}
let decoder = JSONDecoder()
// decodes the Server response
guard let response = try? decoder.decode(ApiResponse.self, from: data) else {
print("Something happen here")
completion(.failure(AppError.errorDecoding))
return
}
// Returns if an error occurs
if let error = response.errors {
completion(.failure(AppError.serverError(error)))
return
}
// Decodes the data received from server
if let decodedData = response.results {
completion(.success(decodedData as! T))
} else {
completion(.failure(AppError.errorDecoding))
}
case .failure(let error):
completion(.failure(error))
}
}
If anyone could help me understand why my code isnt working that would be much appreciated.
Your struct is wrong. Try using this?
struct YourAPIData {
struct Root: Codable {
struct Result: Codable {
struct Login: Codable {
let isAuthorized: Bool
let isEmpty: Bool
struct UserName: Codable {
let isEmpty: Bool
struct Name: Codable {
let firstName: String
let lastName: String
let displayName: String
let isEmpty: Bool
let fullName: String
}
let name: Name
let canDelete: Bool
let id: Int
let canModify: Bool
}
let userName: UserName
let username: String
}
let login: Login
struct Parameters: Codable {
let isEmpty: Bool
let keepLoggedInIndicator: Bool
let username: String
private enum CodingKeys: String, CodingKey {
case isEmpty
case keepLoggedInIndicator = "keep_logged_in_indicator"
case username
}
}
let parameters: Parameters
}
let result: Result
let isAuthorized: Bool
struct Version: Codable {
let major: String
let minor: String
let fix: String
let display: Date
let isEmpty: Bool
}
let version: Version
let isSystemDown: Bool
let timestamp: String
let isSuccess: Bool
}
}
and try using
do {
let APIData = try JSONDecoder().decode(YourAPIData.Root.self, from: jsonData)
} catch let jsonErr { print("Error: ", jsonErr) }
And if you would like to display your json (for testing, i suppose?)
if let data = jsonData, let body = String(data: jsonData, encoding: .utf8) {
print(body)
}
} else {
print(error ?? "Unknown error")
}

can not convert json to struct object

I want to parse JSON data into a struct object but i can't do it.
Actually the code is in different files but here i'm posting it as a whole.
Here is my code :
import Foundation
struct dataResponse: Decodable {
var results: [userData]
init(from decoder: Decoder) throws {
var results = [userData] ()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(userData.self) {
results.append(route)
}
else {
_ = try? container.decode(dummyData.self)
}
}
self.results = results
}
}
private struct dummyData: Decodable { }
enum dataError: Error {
case dataUnavailable
case cannotProcessData
}
struct userData: Codable {
var avatar: String
var city: String
var contribution: Int
var country: String
var friendOfCount: Int
var handle: String
var lastOnlineTimeSeconds: Int
var maxRank: String
var maxRating: Int
var organization: String
var rank: String
var rating: Int
var registrationTimeSeconds: Int
var titlePhoto: String
}
struct dataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<[userData], dataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
// let dataresponse = try JSONDecoder().decode([userData].self, from: data)
// print(type(of: dataresponse))
// completionHandler(.success(dataresponse))
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
completionHandler(.success(jsonResult as! [userData]))
}
catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
here userData is my struct
the error says : Could not cast value of type '__NSDictionaryM' (0x7fff8fe2dab0) to 'NSArray' (0x7fff8fe2dd30).
I would appreciate if anyone helps, thanks.
You are making a very common mistake.
You are ignoring the root object which is a dictionary and causes the error.
struct Root: Decodable {
let status : String
let result: [UserData]
}
struct UserData: Decodable {
let avatar: String
let city: String
let contribution: Int
let country: String
let friendOfCount: Int
let handle: String
let lastOnlineTimeSeconds: Int
let maxRank: String
let maxRating: Int
let organization: String
let rank: String
let rating: Int
let registrationTimeSeconds: Int
let titlePhoto: String
}
Forget JSONSerialization and use only JSONDecoder
And it's not a good idea to return meaningless enumerated errors. Use Error and return the real error.
You get the array with dataresponse.result
struct DataRequest { // name structs always with starting capital letter
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Root, Error>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(error!))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
let dataresponse = try JSONDecoder().decode(Root.self, from: data)
completionHandler(.success(dataresponse))
}
catch {
completionHandler(.failure(error))
}
}.resume()
}
}
And consider that if status is not "OK" the JSON response could be different.
Your problem is you are using the wrong struct. Create and use a Response struct for decoding. You need to keep your Types to have the first letter in capital to avoid confusion. You could use the JSONDecoder() maybe you are using something that doesn't have the correct format. Try the below code.
struct Response: Codable {
var result: [UserData]
}
enum DataError: Error {
case dataUnavailable, cannotProcessData
}
struct DataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Response, DataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
return
}
do {
let dataresponse = try JSONDecoder().decode(Response.self, from: data)
completionHandler(.success(dataresponse))
} catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
Note: Parameters in UserData always needs to right. Try with and Empty struct to check if everything else is working then proceed to adding the variable one-by-one.

Swift.DecodingError.Context

i am decoding JSON (Decoder) string from URL repsonse as follows and get a get during the decoding process the following error messages:
Error:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "tags", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
Structure:
struct Wordpress: Codable {
let id: Int?
let date: String?
let date_gmt: String?
let guid: GUID?
let modified: String?
let modified_gmt: String?
let slug: String?
let status: String?
let type: String?
let link: String?
let title: Title?
let content: Content?
let excerpt: Excerpt?
let author: Int?
let featured_media: Int?
let comment_status: String?
let ping_status: String?
let sticky: Bool?
let template: String?
let format: String?
let meta: [String]?
let categories: [Int]?
let tags: [String]?
let _links: Link?
enum CodingKeys: String, CodingKey {
case id = "id"
case date = "date"
case date_gmt = "date_gmt"
case guid = "guid"
case modified = "modified"
case modified_gmt = "modified_gmt"
case slug = "slug"
case status = "status"
case type = "type"
case link = "link"
case title = "title"
case content = "content"
case excerpt = "excerpt"
case author = "author"
case featured_media = "featured_media"
case comment_status = "comment_status"
case ping_status = "ping_status"
case sticky = "sticky"
case template = "template"
case format = "format"
case meta = "meta"
case categories = "categories"
case tags = "tags"
case _links = "_links"
}
}
struct GUID: Codable{
let rendered: String?
}
struct Title: Codable{
let rendered: String?
}
struct Content: Codable{
let rendered: String?
let protected: Bool?
}
struct Excerpt: Codable{
let rendered: String?
let protected: Bool?
}
struct Link: Codable {
let urls: [URLString]?
let collection: [Collection]?
let about: [About]?
let author: [Author]?
let replies: [Replies]?
let versionHistory: [Version]?
let predecessorVersion: [Predecessor]?
let wpFeaturedmedia: [Featured]?
let wpAttachment: [Attachment]?
let wpTerm: [Term]?
let curies: [Curies]?
enum CodingKeys: String, CodingKey {
case urls = "urls"
case collection = "collection"
case about = "about"
case author = "author"
case replies = "replies"
case versionHistory = "version-history"
case predecessorVersion = "predecessor-version"
case wpFeaturedmedia = "wp:featuredmedia"
case wpAttachment = "wp:attachment"
case wpTerm = "wp:term"
case curies = "curies"
}
}
struct About: Codable{
let href : String?
}
struct Author: Codable {
let embeddable: Bool?
let href : String?
}
struct Replies: Codable {
let embeddable: Bool?
let href : String?
}
struct Version: Codable {
let count: Int?
let href : String?
}
struct Predecessor: Codable {
let id: Int?
let href : String?
}
struct Featured: Codable {
let embeddable: Bool?
let href : String?
}
struct Attachment: Codable{
let href : String?
}
struct Term: Codable {
let taxonomy: String?
let embeddable: Bool?
let href: String?
}
struct Curies: Codable {
let name: String?
let href: String?
let templated: Bool?
}
My decode code:
class func path_getPosts(per_page:Int) -> URL {
let s = APIwp.base + APIwp.posts + APIwp.per_page + "\(per_page)"
print(s)
return URL(string: s)!
}
static func getPosts(for per_page: Int, completion: #escaping ([Wordpress], Error?) -> Void) {
taskGetPosts(url: path_getPosts(per_page: per_page), responseType: [Wordpress].self) { (response, error) in
if let response = response {
completion(response, nil)
} else {
completion([], error)
}
}
}
#discardableResult static func taskGetPosts<ResponseType: Decodable>(url: URL, responseType: ResponseType.Type, completion: #escaping (ResponseType?, Error?) -> Void) -> URLSessionDataTask {
let task = URLSession.shared.dataTask(with: url) { (data, response, error)
in
guard let data = data else {
DispatchQueue.main.async {
completion(nil, error)
}
return
}
let decoder = JSONDecoder()
do {
let responseObject = try decoder.decode(ResponseType.self, from: data)
DispatchQueue.main.async { completion(responseObject, nil) }
} catch let jsonErr {
print("Error:=: \(jsonErr)")
DispatchQueue.main.async { completion(nil, error) }
}
}
task.resume()
return task
}
Please read the error message carefully.
It tells you that the first item (_JSONKey(stringValue: "Index 0", intValue: 0)) in the array for key tags (CodingKeys(stringValue: "tags", intValue: nil) in the first item of the root object (_JSONKey(stringValue: "Index 0", intValue: 0) is not a string, it's a number (Expected to decode String but found a number instead).
So tags is probably [Int]

Data from url doesn't load. Although URL is correct

DispatchQueue.global().async{
do{
let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json")!
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let downloadedFriends = try decoder.decode([Friend].self, from: data)
DispatchQueue.main.async {
self.friends = downloadedFriends
self.tableView.reloadData()
}
}catch{
print(error.localizedDescription)
}
}
It Prints: "The data couldn’t be read because it is missing."
I checked url hackingwithswift and it's correct
I think the problem is in your Friend struct. You need to checkout that every field is correct. Although I highly recommend to use app.quicktype.io to automatically generate Codable Structures
Here is correct Friend structure:
// MARK: - Friend
struct Friend: Codable {
var id: String?
var isActive: Bool?
var name: String?
var age: Int?
var company, email, address, about: String?
var registered: Date?
var tags: [String]?
var friends: [FriendElement]?
}
// MARK: - FriendElement
struct FriendElement: Codable {
var id, name: String?
}
Shynggys, I am also working with the JSONDecoder for the moment, and compared your code to mine. I made a small change to your code in the async method of the DispatchQueue class, and got data back from the URL. Line 30 in the screen print.
I also used the Friend struct which David suggested, since you did not include all your code in the post.
import UIKit
struct Friend: Codable {
var id: String?
var isActive: Bool?
var name: String?
var age: Int?
var company, email, address, about: String?
var registered: Date?
var tags: [String]?
var friends: [FriendElement]?
}
// MARK: - FriendElement
struct FriendElement: Codable {
var id, name: String?
}
DispatchQueue.global().async{
do{
let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json")!
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let downloadedFriends = try decoder.decode([Friend].self, from: data)
DispatchQueue.main.async {
let friends = downloadedFriends //remove self, and assign to friends
// self.tableView.reloadData()
print (friends)
}
}catch{
print(error.localizedDescription)
}
}
Results in console: