Classes or Structs for Models in Swift - swift


I want to implement IgListKit for my UICollectionView. This Library requires me to use “Class Model : ListDiffable”
Accoding to my current architecture I have “Struct Model : Decodable” As I use JSON Decoder in my NetworkService to retrieve Data
I have 2 struct, 1 for the Root and the 2 for my Array.
struct Talents : Decodable {
let status : String?
let error : String?
let response : String?
}
struct Talent: Decodable {
let id: String
let name : String
let smallDesc: String
let largeDesc : String
}
\\I also have enum CodingKeys to match the keys for both structs
Following is the Struct output ,Works well to be used in my UICollectionView
when I change these structs to class
class Talents : Decodable {
var status : String?
var error : String?
var response : String?
init( status : String,error : String, response : String){
self.status = status
self.error = error
self.response = response
}
}
This is the Class Output I get, Which I am not sure how to use.
What are the changes I should make in order to resolve this, and apply : ListDiffable Protocol stubs to my Model class?
Service File with the instance of which in viewDidLoad of my CollectionVC I take the Data in an array.
static func getCategoryTalents(category:String,completion: #escaping (Bool, [Talent]?, Error?) -> Void) {
let parameters: Parameters = [
"filter": category,
"shuffle": 1
]
AF.request(Constants.baseUrl,
parameters : parameters ).response { response in
guard let data = response.data else {
DispatchQueue.main.async {
print("\(Error.self)")
completion(false, nil, Error.self as? Error)
}
return}
do {
let talentsResponse = try JSONDecoder().decode(Talents.self, from: data)
print(talentsResponse)
let firstJSONString = talentsResponse.response?.replacingOccurrences(of: "\\", with: "")
let secondJSONString = firstJSONString?.replacingOccurrences(of: "\"{", with: "{").replacingOccurrences(of: "}\"", with: "}")
guard let talentArray = try! JSONDecoder().decode([Talent]?.self, from: (secondJSONString?.data(using: .utf8)!)!) else {
return }
print(talentArray)
var talents = [Talent]()
for talent in talentArray {
guard let individualTalent = talent as Talent? else { continue }
talents.append(individualTalent)
}
DispatchQueue.main.async {
completion(true, talents, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
}

You don't need to change the existing struct to class create a new class and make an initializer which accepts struct as the parameter:
struct TalentDataModel: Decodable {
let status : String?
let error : String?
let response : String?
}
class Talents: Decodable {
var status : String?
var error : String?
var response : String?
init(dataModel: TalentDataModel) {
status = dataModel.status
error = dataModel.error
response = dataModel.response
}
}

Since it is a whole lot of work to make serve Models with srtucts as seen here
I changed my class to make it work with IGListKit.
import Foundation
import IGListKit
class Talents: NSObject,Decodable {
let status : String
let error : String
let response : String
init(status:String,error:String,response:String) {
self.status = status
self.error = error
self.response = response
}
}
extension NSObject: ListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
return isEqual(object)
}
}

Related

No exact matches in call to class method 'jsonObject'?

Hello I'm new in Swift and I try to write a parser for json through extension with JsonSerialization
I want to implement a function to parse json file and in the same extension to implement the computable var json, which will form json
// Model
struct Todoitem {
let id = {
return UUID().uuidString
}()
let text : String
let importance : Importance
let deadline : Date?
}
enum Importance {
case usual, important, unimportant
}
//Parser
extension Todoitem {
static func parse(json : Any) -> Todoitem? {
if let file = try? JSONSerialization.jsonObject(with: json) as? [String : Any] {
}
return nil
}
}

Passing server response to ViewModel MVVM

I have been trying to pass the response of my AF.request statement to my viewModel but am unable to understand still? I need to pass my response to my ViewModel and then use it to display in the tableView.
This is my Service Class:
Service
class Service {
fileprivate var baseUrl = ""
//https://api.themoviedb.org/3/tv/76479?api_key=3d0cda4466f269e793e9283f6ce0b75e&language=en-US
init(baseUrl: String) {
self.baseUrl = baseUrl
}
var tvShowDetails = TVShowModel()
func getTVShowDeet(completionHandler: #escaping ()-> TVShowModel){
let request = AF.request(self.baseUrl)
.validate()
.responseDecodable(of: TVShowModel.self) { (response) in
guard let tvShow = response.value else {return}
return tvShow
print("printing response", tvShow)
}
}
}
ViewModel
func getTVShowDetails(){
service.getTVShowDeet{
print(self.response)
self.delegate?.reloadTable()
self.headerDelegate?.configureHeader()
print("prinitn respinse in VM", self.response)
}
}
Model
struct TVShowModel : Decodable {
let id : Int?
let original_name : String?
let overview : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case original_name = "original_name"
case overview = "overview"
}
init(){
id = nil
original_name = nil
overview = nil
}
}
Networking requests are asynchronous meaning we don't know when they'll complete so we need to use completion handlers instead of returning from the function (unless you use Async/Await). Something along the lines of this should work:
Service
func getTVShowDeet(completionHandler: #escaping (TVShowModel) -> Void) {
let request = AF.request(self.baseUrl)
.validate()
.responseDecodable(of: TVShowModel.self) { (response) in
guard let tvShow = response.value else { return }
completionHandler(tvShow)
}
}
ViewModel
func getTVShowDetails() {
service.getTVShowDeet { [weak self] tvShow in
// Here you may need to store the tvShow object somewhere to use in your tableView datasource.
self?.delegate?.reloadTable()
self?.headerDelegate?.configureHeader()
}
}

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.

Extending a class model for generic type Swift

I am passing API response with Moya and getting this value. I am able to get the object but I extented a base response to handle extra parameters but the extended value does not seem to work. The data expected could be an array of objects and it could just be a regular object. After passing this values, It stopped working and data is not got but every other parameter like status , message are passed except data. Here is my Base response and how I used it
class MaxResponseBase: Codable {
var status: String?
var message: String?
var pagination: Pagination?
var isSucessful: Bool {
return status == "success"
}
struct ErrorMessage {
static let passwordInvalid = " Current password is invalid."
static let loginErrorIncorrectInfo = " Incorrect username/password."
static let loginErrorAccountNotExist = " Invalid request"
}
}
class MaxResponse<T: Codable>: MaxResponseBase {
var data: T?
}
class MaxArrayResponse<T: Codable>: MaxResponseBase {
var data = [T]()
}
Here is my API call for signin for example
func signin(email: String, password: String) -> Observable<MaxResponse<AuthResponse>> {
return provider.rx.request(.signin(username: email, password: password))
.filterSuccess()
.mapObject(MaxResponse<AuthResponse>.self)
.asObservable()
}
how can I tweak this to get data object also
JSON
{
"status" : "success",
"data" : {
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"
}
}
It could also be an array of data
(Note: all the code below can be put in a Playground to show that it works.)
In order to solve this, you have to manually write all your initializers. I posted the code that does most of it below but I strongly recommend you use structs instead of classes. It is better in every way if you use structs and containment instead of classes and inheritance.
struct Pagination: Codable { }
struct AuthResponse: Codable {
let isLocked: Bool
let __v: Int
let createdAt: Date
}
class MaxResponseBase: Codable {
let status: String?
let message: String?
let pagination: Pagination?
var isSucessful: Bool {
return status == "success"
}
struct ErrorMessage {
static let passwordInvalid = " Current password is invalid."
static let loginErrorIncorrectInfo = " Incorrect username/password."
static let loginErrorAccountNotExist = " Invalid request"
}
enum CodingKeys: String, CodingKey {
case status, message, pagination
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String?.self, forKey: .status)
message = try? container.decode(String?.self, forKey: .message) ?? nil
pagination = try? container.decode(Pagination?.self, forKey: .pagination) ?? nil
}
}
class MaxResponse<T: Codable>: MaxResponseBase {
let data: T?
enum DataCodingKeys: String, CodingKey {
case data
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DataCodingKeys.self)
data = try container.decode(T?.self, forKey: .data)
try super.init(from: decoder)
}
}
let json = """
{
"status" : "success",
"data" : {
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"
}
}
"""
let data = json.data(using: .utf8)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let response = try decoder.decode(MaxResponse<AuthResponse>.self, from: data)
print(response)
It is far simpler and less code to just use a struct:
struct AuthResponse: Codable {
struct ResponseData: Codable {
let isLocked: Bool
let __v: Int
let createdAt: Date
}
let status: String?
let data: ResponseData
}
let json = """
{
"status" : "success",
"data" : {
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"
}
}
"""
let data = json.data(using: .utf8)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let response = try decoder.decode(AuthResponse.self, from: data)
print(response)
And if you really need the MaxResponse type, then make it a protocol and have your other types conform to it. I'm almost willing to bet that you don't need it though.
In response to your comments, here is a generic solution using structs:
struct LoginResponseData: Codable {
let isLocked: Bool
let __v: Int
let createdAt: Date
}
struct BlogResponseData: Codable {
let xxx: Bool
let yyy: Int
let createdAt: Date
}
struct BaseRresponse<ResponseData: Codable>: Codable {
let status: String?
let data: ResponseData
}

how prepare API response to use with jsonDecoder in swift

when i call API and get response from server with Alamofire, i want use "data" object from json
this data come from API
{
"code": 200,
"hasError": false,
"data": [
{
"userSession": "43a1bd70-26bf-11e9-9ccd-00163eaf6bb4"
}
],
"message": "ok"
}
and i want map data to my AuthModel
this is my AuthModel:
struct AuthModel: Codable {
let userSession: String
enum CodingKeys: String, CodingKey {
case userSession = "userSession"
}
}
i coded this lines but it isn't work:
if let responseObject = response.result.value as? Dictionary<String,Any> {
if let hasError = responseObject["hasError"] as? Bool {
guard !hasError else { return }
do {
let decoder = JSONDecoder()
let authModel = try decoder.decode(AuthModel.self, from: responseObject["data"])
} catch {
print("Parse Error: ",error)
}
}
}
this does not work because responseObject["data"] is not NSData Type
Cannot convert value of type '[String : Any]' to expected argument type 'Data'
I think your API response is a pattern that indicates:
Do we have any problem (error)?
Do we have our expected data?
Based on these, we can use Enum and Generics. For example:
class ResponseObject<T: Codable>: Codable {
private var code : Int
private var hasError : Bool
private var message : String
private var data : T?
var result: Result {
guard !hasError else { return .error(code, message) }
guard let data = data else { return .error(0, "Data is not ready.") }
return .value(data)
}
enum Result {
case error(Int, String)
case value(T)
}
}
and we can use ResponseObject with our expected data:
let responseString = """
{
"code": 200,
"hasError": false,
"data": [
{
"userSession": "43a1bd70-26bf-11e9-9ccd-00163eaf6bb4"
}
],
"message": "ok"
}
"""
class AuthObject: Codable {
var userSession : String
}
if let jsonData = responseString.data(using: .utf8) {
do {
//ResponseObject<[AuthObject]> means: if we don't have error, the `data` object in response, will represent `[AuthObject]`.
let responseObject = try JSONDecoder().decode(ResponseObject<[AuthObject]>.self, from: jsonData)
//Using ResponseObject.Result Enum: We have error with related code and message, OR, we have our expected data.
switch responseObject.result {
case .error(let code, let message):
print("Error: \(code) - \(message)")
case .value(let authObjects):
print(authObjects.first!.userSession)
}
} catch {
print(error.localizedDescription)
}
}
Get the Data response rather than the deserialized Dictionary for example
Alamofire.request(url).responseData { response in
and decode
let decoder = JSONDecoder()
let authModel = try decoder.decode(AuthModel.self, from: response.data!)
into these structs
struct AuthModel : Decodable {
let code : Int
let hasError : Bool
let message : String
let data : [Session]
}
struct Session : Decodable {
let userSession: String
}
All CodingKeys are synthesized.