1
votes

There is a way to make my on code function for my codable struct, the problem I'm facing is that I can encode it from network but not decode from userdefaults. Here is the implementation of the struct.

struct profile: Codable{
    var id: Int?
    var firstName: String?
    var lastName: String?
    var description: String?
    var gender: String?
    var birthDate: Date?
    var smoke: Bool?
    var findHouse: Bool?
    var hasRoom: Bool?
    var sociable: Int?
    var ordered: Int?
    var athlete: Int?
    var party: Int?
    var domestic:Int?
    var gamer: Int?
    var usersId: Int?
    var imageId: Int?
    var createdAt: Date?
    var updatedAt: Date?
    var image: imageModel?
    var tags: [String]?

    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName =  "last_name"
        case description
        case gender
        case birthDate = "birth_date"
        case smoke
        case findHouse = "find_house"
        case hasRoom = "has_room"
        case sociable
        case ordered
        case athlete
        case party
        case domestic
        case gamer
        case usersId = "users_id"
        case imageId = "image_id"
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case image
        case tags
    }
    init(){

    }
    init(from decoder: Decoder) throws {
        print("going to decode from decoder showing results")
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.id = (try values.decodeIfPresent(Int.self, forKey: .id))
        print("id value \(self.id)")
        self.firstName = (try values.decodeIfPresent(String.self, forKey: .firstName))
        print("name value \(self.firstName)")
        self.lastName = (try values.decodeIfPresent(String.self, forKey: .lastName))
        print("lastname value \(lastName)")
        self.description = (try values.decodeIfPresent(String.self, forKey: .description))
        print("description value \(description)")
        self.gender = (try values.decodeIfPresent(String.self, forKey: .gender))
        print("gender value \(gender)")
        if let date = try values.decodeIfPresent(String.self, forKey: .birthDate){
            print("date value \(date)")
            self.birthDate = Date(fromString: String(date.dropLast(10)), format: .custom("yyyy-MM-dd"))
            print("after conversion \(self.birthDate)")
        }else{
            self.birthDate = nil
        }
        self.smoke = (try values.decodeIfPresent(Int.self, forKey: .smoke)) == 0 ? false : true
        print("smoke value \(smoke)")
        self.findHouse = (try values.decodeIfPresent(Int.self, forKey: .findHouse)) == 0 ? false : true
        print("findhouse value \(findHouse)")
        self.hasRoom = (try values.decodeIfPresent(Int.self, forKey: .hasRoom)) == 0 ? false : true
        print("has romm value \(hasRoom)")
        self.sociable = (try values.decodeIfPresent(Int.self, forKey: .sociable))
        print("sociable value\(sociable)")
        self.ordered = (try values.decodeIfPresent(Int.self, forKey: .ordered))
        print("orderder value \(ordered)")
        self.athlete = (try values.decodeIfPresent(Int.self, forKey: .athlete))
        print("athlete value \(athlete)")
        self.party = (try values.decodeIfPresent(Int.self, forKey: .party))
        self.domestic = (try values.decodeIfPresent(Int.self, forKey: .domestic))
        self.gamer = (try values.decodeIfPresent(Int.self, forKey: .gamer))
        self.usersId = (try values.decodeIfPresent(Int.self, forKey: .usersId))
        self.imageId = (try values.decodeIfPresent(Int.self, forKey: .imageId))
        if let date = try values.decodeIfPresent(String.self, forKey: .createdAt){
            self.createdAt = Date(fromString: String(date.dropLast(10)), format: .custom("yyyy-MM-dd"))
        }else {
            self.createdAt = nil
        }
        if let date = try values.decodeIfPresent(String.self, forKey: .updatedAt){
            self.updatedAt = Date(fromString: String(date.dropLast(10)), format: .custom("yyyy-MM-dd"))
        }else{
            self.updatedAt = nil
        }
        self.image = (try values.decodeIfPresent(imageModel.self, forKey: .image))
        self.tags = (try values.decodeIfPresent([String].self, forKey: .tags))
    }

}

This is the code to save to userdefaults.standard:

let encoder = JSONEncoder()
    if let encoded = try? encoder.encode(requestManager.instance.user) {
        UserDefaults.standard.set(encoded, forKey: "user")
        if let json = String(data: encoded, encoding: .utf8) {
            print("reading value in userDefault \(json)")
        }
    }

so when I try to decode from userDefaults with this code:

let decoder = JSONDecoder()
        if let user = UserDefaults.standard.data(forKey: "user"){
            print("data for user \(user)")
            do{
                let question = try decoder.decode(profile.self, from: user)
                print("birthDate from decoder \(question.birthDate)")
                print("id from decoder \(question.id)")
                print("name from decoder \(question.firstName)")
                if question.id != nil {
                    print("the id is not Nil \(question)")
                    requestManager.instance.user = question
                }

            }catch{
                print(error.localizedDescription)
                print("el valor de birthDate in \(requestManager.instance.user.birthDate)")
            }
        }

After this I got those messages in console

going to decode from decoder. id value Optional(3). name value Optional("Yoel "). Data cannot be read because does not have the right format.

What I guess is date type in fields birthDate, createdAt and updatedAt.

So what I want to try is to create a custom encoder because in decoder is expecting a String and should be returning a Date value.

1
If you change the types in init(from you have to do the opposite in encode( - vadian
is not necesary to create a encode funtion i allready did the encode funtion but that not solve the main problem of saving to userdefaulft. - Yoel Jimenez del valle
It is indeed necessary. For example you encode hasRoom as Bool (with the synthesized encoder) but you decode it as Int which causes a type mismatch. With respect and no offense but your entire struct is a mess. 😉 - vadian
the problem here is what i got from endpoint if was a bool value true or false that would be nice but indeed is bool value with 0 and 1 so if i make then Int have to make a validation for only 0 and 1 so is clear a bool just not retorned from endpoint - Yoel Jimenez del valle
Nevertheless if you use the full Codable protocol all encoded and decoded types must match. - vadian

1 Answers

0
votes

Date conforms to the codable Protocol on its own. Why do you try to do some custom decoding on your own in your init(from decoder: Decoder)? You should not try to decode it as a string. I just tried a very small implementation like this:

//: Playground - noun: a place where people can play

import Foundation

var str = "Hello, playground"

struct Person:Codable {
    var name:String
    var birthDate:Date
}
func test (){
    let pascal = Person(name: "Pascal", birthDate: Date(timeIntervalSinceNow: -26.6 * 365 * 24 * 3600))
    print(pascal)
    let encoder = JSONEncoder()
    if let encoded = try? encoder.encode(pascal) {
        UserDefaults.standard.set(encoded, forKey: "person")
    }

    let decoder = JSONDecoder()

    guard let data = UserDefaults.standard.data(forKey: "person"),
          let person = try? decoder.decode(Person.self, from: data) else {
             print("damn")
             return
    }
    print(person)
}

test()