Conforming to Decodable Failing for a struct - ios

I have the following struct:
struct Category: Codable, Hashable {
let id: Int
let name: String
let image: String
}
I also have a Resource struct as shown below:
struct Resource<T: Codable> {
let url: URL
var headers: [String: String] = [:]
var method: HttpMethod = .get([])
let modelType: T
}
When I try to create an instance of Resource, it fails:
let resource = Resource(url: URL.allCategories, modelType: Category.self)
// Type 'Category.Type' cannot conform to 'Decodable'
UPDATE:
enum HttpMethod {
case get([URLQueryItem])
case post(Data?)
case delete
var name: String {
switch self {
case .get:
return "GET"
case .post:
return "POST"
case .delete:
return "DELETE"
}
}
}
SOLUTION:
I believe the modelType should be T.Type instead of T. I made the change and now I am not getting any errors.
struct Resource<T: Codable> {
let url: URL
var headers: [String: String] = [:]
var method: HttpMethod = .get([])
let modelType: T.Type
}

Related

How to debug JSONDecoder when no errors show?

I'm building a Swift app and testing in an Xcode Playground. Calling the NYTimes Search API and trying to store its response in a struct. The code executes cleanly and no errors appear (I am using a do, try, catch), but I cannot print any properties from the resulting object (print(json.status)).
My hunch is that something is fishy with this line but I'm not sure what since no errors are printing from the catch statement
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
Form the URL endpoint to make the API call:
func APICall() {
let APIKey = "MY_API_KEY_GOES_HERE_BUT_IT'S_A_SECRET"
let searchTerm = "A Super Bowl Sideshow: See the Ageless Man!"
// Remove the spaces and convert them to percents
guard let encodedSearchTerm = searchTerm.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
else {
print("Error encoding search term in URL")
return
}
let url = "https://api.nytimes.com/svc/search/v2/articlesearch.json?q=" + encodedSearchTerm + "&api-key=" + APIKey
getData(from: url)
}
Data Task:
func getData(from url: String) {
//I believe something is wrong with the following line but I'm not sure what it is
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("Error loading data")
return
}
var result: NYTSearchResponse?
do {
result = try JSONDecoder().decode(NYTSearchResponse.self, from: data)
} catch {
print(error)
}
guard let json = result else {
print("Error assigning result to json")
return
}
//Try to print these from the resulting object but these commands do not print
print(json.status)
print(json.response.docs[0].abstract)
})
task.resume()
}
My NYTSearchResponse struct which mirrors the NYT API JSON response. It's pretty complicated, but I pasted the json response into https://app.quicktype.io/ to build the struct.
// MARK: - Welcome
struct NYTSearchResponse: Codable {
let status, copyright: String
let response: Response
}
// MARK: - Response
struct Response: Codable {
let docs: [Doc]
let meta: Meta
}
// MARK: - Doc
struct Doc: Codable {
let abstract: String
let webURL: String
let snippet, leadParagraph, printSection, printPage: String
let source: String
let multimedia: [Multimedia]
let headline: Headline
let keywords: [Keyword]
let pubDate: Date
let documentType, newsDesk, sectionName, subsectionName: String
let byline: Byline
let typeOfMaterial, id: String
let wordCount: Int
let uri: String
enum CodingKeys: String, CodingKey {
case abstract
case webURL = "web_url"
case snippet
case leadParagraph = "lead_paragraph"
case printSection = "print_section"
case printPage = "print_page"
case source, multimedia, headline, keywords
case pubDate = "pub_date"
case documentType = "document_type"
case newsDesk = "news_desk"
case sectionName = "section_name"
case subsectionName = "subsection_name"
case byline
case typeOfMaterial = "type_of_material"
case id = "_id"
case wordCount = "word_count"
case uri
}
}
// MARK: - Byline
struct Byline: Codable {
let original: String
let person: [Person]
let organization: String?
}
// MARK: - Person
struct Person: Codable {
let firstname: String
let middlename: String?
let lastname: String
let qualifier, title: String?
let role, organization: String
let rank: Int
}
// MARK: - Headline
struct Headline: Codable {
let main: String
let kicker, contentKicker: String?
let printHeadline: String
let name, seo, sub: String?
enum CodingKeys: String, CodingKey {
case main, kicker
case contentKicker = "content_kicker"
case printHeadline = "print_headline"
case name, seo, sub
}
}
// MARK: - Keyword
struct Keyword: Codable {
let name, value: String
let rank: Int
let major: String
}
// MARK: - Multimedia
struct Multimedia: Codable {
let rank: Int
let subtype: String
let caption, credit: String?
let type, url: String
let height, width: Int
let legacy: Legacy
let subType, cropName: String
enum CodingKeys: String, CodingKey {
case rank, subtype, caption, credit, type, url, height, width, legacy, subType
case cropName = "crop_name"
}
}
// MARK: - Legacy
struct Legacy: Codable {
let xlarge: String?
let xlargewidth, xlargeheight: Int?
}
// MARK: - Meta
struct Meta: Codable {
let hits, offset, time: Int
}
I moved the code out of the playground and it works.

Assign JSON Response to Model SwiftUI

I have successfully parsed JSON by creating a Model. But now I want a View Model which talks to View and the View updates whenever the Model Updates. But I m having hard time initialising the model.
My simplified model looks like this.
PeopleListM.swift
import SwiftUI
import Alamofire
struct PeopleListM: Decodable {
var all: [Person]
enum CodingKeys: String, CodingKey {
case all = "people"
}
struct Person: Decodable {
let name: String
let email: String
let favorite: Bool
let lastSent: String?
enum CodingKeys: String, CodingKey {
case name
case email = "email_id"
case favorite
case lastSent = "last_sent"
}
}
init() {
let headers: HTTPHeaders = ["disableauth" : "true", "Content-Type": "application/json"]
AF.request("MY URL", parameters: nil, headers: headers)
.validate()
.responseDecodable(of: PeopleListM.self) { (response) in
guard let result = response.value else { return }
self.all = result.all
}
}
This code gives me error Escaping closure captures mutating 'self' parameter
I tried changing line self.all = result.all with self = result, same error.
My further plan is create a class which conforms to ObservableObject
PeopleListVM.swift
import SwiftUI
class PeopleListVM: ObservableObject {
let people = PeopleListVM.createModel()
static func createModel() {
PeopleListM()
}
}
final class PeopleListVM: ObservableObject {
#Published var people = PeopleListVM.createModel()
static func createModel() {
PeopleListM()
}
func request() {
// networking here...
// update people in callback
// ... self.people = newPeople
}
}
struct Model: View {
#ObservableObject var vm = PeopleListVM()
// ...
}
You generally won't put networking code in struct, because networking always comes with state changes, when value type is meant to be immutable.

Error trying to return protocol associated type from method

I need to search for entities(photos, albums, etc) using unsplash.com search APIs. In order to reach reusability and not having to create a new function every time i need to search for a new entity i created a protocol:
protocol SearchApiResource {
associatedtype ModelType: Decodable
var methodPath: String { get }
var searchTerm: String { get set }
var pageNumber: Int { get set }
var parameters: [String: String] { get }
var url: URL { get }
}
And a structure conforming to that protocol:
struct SearchPhotoResource: SearchApiResource {
typealias ModelType = Photo
var methodPath = "/search/photos"
var searchTerm = String()
var pageNumber = Int()
let itemsPerPage = 30
let accessKey = "93e0a185df414cc1d0351dc2238627b7e5af3a64bb228244bc925346485f1f44"
var parameters: [String: String] {
var params = [String: String]()
params["query"] = searchTerm
params["page"] = String(pageNumber)
params["per_page"] = String(itemsPerPage)
params["client_id"] = accessKey
return params
}
var url: URL {
var components = URLComponents()
components.scheme = "https"
components.host = "api.unsplash.com"
components.path = methodPath
components.queryItems = parameters.map {URLQueryItem(name: $0, value: $1)}
return components.url!
}
}
Now I want to create a function that will accept an struct or class conforming to SearchApiResource protocol:
func searchForItem(resource: SearchApiResource, searchTerm: String, pageNumber: Int, completion: #escaping (SearchApiResource.ModelType) -> Void ) {
}
But I receive an error:
"Associated type 'ModelType' can only be used with a concrete type or generic parameter base"
How to fix an error and what am i doing wrong?
Just do what the error says - use SearchApiResource as a generic parameter base.
func searchForItem<T: SearchApiResource>(resource: T, searchTerm: String, pageNumber: Int, completion: #escaping (T.ModelType) -> Void ) {
}

Swift 4 Codable : Common struct for all model

Here i am getting API response of all of my api.
{
"success" : true,
"message" : "",
"data" : {
/multipal data parameter/
}
}
And here is my codable model
struct Login: Codable {
let success: Bool
let message: String
let data: Data
struct Data: Codable {
}
}
How can i create common Sturct for success and message parameter.
You can make the root struct representing the network response generic, this will allow you to keep the success and message parts common between all specialised responses.
struct NetworkResponse<ResponseData:Codable>: Codable {
let success: Bool
let message: String
let data: ResponseData
}
You shouldn't create custom types with the same name as built in types, since that will lead to confusion, especially for other people reading your code, so I renamed your custom Data type to ResponseData.
For instance you can create a LoginResponse model and decode it like below. You can do the same for other responses from the same API.
let loginResponse = """
{
"success" : true,
"message" : "",
"data" : {
"username":"test",
"token":"whatever"
}
}
"""
struct LoginResponse: Codable {
let username: String
let token: String
}
do {
print(try JSONDecoder().decode(NetworkResponse<LoginResponse>.self, from: Data(loginResponse.utf8)))
} catch {
print(error)
}
Common structure :
I have created something like that
struct statusModel<T:Codable>: Codable {
let message : String
let resultData : [T]?
let status : Int
enum CodingKeys: String, CodingKey {
case message = "message"
case resultData = "resultData"
case status = "status"
}
}
Regular model (resultData)
struct modelInitialize : Codable {
let profileimgurl : String?
let projecturl : String?
enum CodingKeys: String, CodingKey {
case profileimgurl = "profileimgurl"
case projecturl = "projecturl"
}
}
You can set like as below
do {
guard let reponseData = responseData.value else {return} //Your webservice response in Data
guard let finalModel = try?JSONDecoder().decode(statusModel<modelInitialize>.self, from: reponseData) else {return}
}

Append into array from JSON API

How can I append into an array using JSON Model Class.
Here is my JSON API Request: https://developer.github.com/v3/search/
import Foundation
typealias GitDecode = [GitDecodeElement]
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Item]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Item: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String
let size, stargazersCount, watchersCount: Int
let language: String
let forksCount, openIssuesCount: Int
let masterBranch, defaultBranch: String
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case masterBranch = "master_branch"
case defaultBranch = "default_branch"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
And here I have my class Model where I'm saying what I want to extract from that response:
import Foundation
struct Git: Codable{
let totalCount: Int
let items: GitItem
init ( totalCount: Int,
itemID: Int, itemDescription: String,
ownerID: Int, ownerAvatarURL: String) {
self.totalCount = totalCount
self.items = GitItem(id: itemID, description: itemDescription, owner: GitOwner(id: ownerID, avatarURL: ownerAvatarURL))
}
}
struct GitItem: Codable{
let id: Int
let description: String
let owner: GitOwner
}
struct GitOwner: Codable {
let id: Int
let avatarURL: String
}
Now I'm stuck when I try to append into my array all my custom properties because itemID, itemDescription, ownerID and ownerAvatarURL are in different classes.
Here is how I'm trying to get all the that properties from JSON using JSONDecoder:
import UIKit
class MainViewController: UIViewController {
var gitRepositoriesArray = [Git]()
override func viewDidLoad() {
super.viewDidLoad()
}
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://developer.github.com/v3/search/")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount,
itemID: <#T##Int#> , itemDescription: <#T##String#>, ownerID: <#T##Int#>, ownerAvatarURL: <#T##String#>))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
}
Complete working playground code:
Makes a search against the API for Swift related repos.
Parses them, adds them to the array and prints out some basic information on each one. (fullName, name, avatarUrl
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Repo]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Repo: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String?
let size, stargazersCount, watchersCount: Int
let language: String?
let forksCount, openIssuesCount: Int
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
var gitRepositoriesArray = [Repo]()
// Download Git Repositories from API
func parseGitRepositories() {
let url = URL(string: "https://api.github.com/search/repositories?q=topic:swift+topic:ios")
var request = URLRequest(url: url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
do {
let gitRepositoriesList = try JSONDecoder().decode(GitDecodeElement.self, from: data!)
gitRepositoriesArray = gitRepositoriesArray + gitRepositoriesList.items
print(gitRepositoriesArray.count)
for repo in gitRepositoriesList.items {
print("\(repo.fullName) - \(repo.name) - \(repo.owner.avatarURL)")
}
} catch {
let str = String(data: data!, encoding: .utf8)
print(str)
print(error)
}
}.resume()
}
parseGitRepositories()
PlaygroundPage.current.needsIndefiniteExecution = true
Output:
30
justjavac/free-programming-books-zh_CN - free-programming-books-zh_CN - https://avatars1.githubusercontent.com/u/359395?v=4
dkhamsing/open-source-ios-apps - open-source-ios-apps - https://avatars0.githubusercontent.com/u/4723115?v=4
matteocrippa/awesome-swift - awesome-swift - https://avatars2.githubusercontent.com/u/475463?v=4
xitu/gold-miner - gold-miner - https://avatars2.githubusercontent.com/u/10482599?v=4
lkzhao/Hero - Hero - https://avatars1.githubusercontent.com/u/3359850?v=4
ReactiveX/RxSwift - RxSwift - https://avatars1.githubusercontent.com/u/6407041?v=4
realm/realm-cocoa - realm-cocoa - https://avatars0.githubusercontent.com/u/7575099?v=4
CocoaPods/CocoaPods - CocoaPods - https://avatars1.githubusercontent.com/u/1189714?v=4
CosmicMind/Material - Material - https://avatars1.githubusercontent.com/u/10069574?v=4
// rest truncated
Notice how I use less models than you do in your code. There is no need to duplicate code, just use the parts you want, when you want them.
There is some problem in you Git structure. I have corrected the initializer like this:
struct Git: Codable{
let totalCount: Int
var items = [GitItem]()
init(totalCount: Int, items: [Item]) {
self.totalCount = totalCount
for item in items {
self.items.append(GitItem(id: item.id, description: item.description, owner: GitOwner(id: item.owner.id, avatarURL: item.owner.avatarURL)))
}
}
So your parsing method will be changed accordingly:
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount, items: eachRepo.items))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
Also note the change of url to https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc. The current url that your are using in your code is just the html page with examples. It doesn't return proper json results.

Resources