如何为可编码对象提供自定义代码功能

Posted

技术标签:

【中文标题】如何为可编码对象提供自定义代码功能【英文标题】:how to provide a custom code func to encodable object 【发布时间】:2018-12-26 13:47:25 【问题描述】:

有一种方法可以使我的 on code 函数适用于我的可编码结构, 我面临的问题是我可以从网络对其进行编码,但不能从用户默认值解码。这是结构的实现。

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


这是保存到 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)")
        
    

所以当我尝试使用此代码从 userDefaults 解码时:

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

在此之后,我在控制台中收到了这些消息

将从解码器解码。 id 值 可选(3)。 名称值可选(“Yoel”)。 由于格式不正确,无法读取数据。

我猜是birthDate、createdAt和updatedAt字段中的日期类型。

所以我想尝试的是创建一个自定义编码器,因为在解码器中需要一个字符串并且应该返回一个日期值。

【问题讨论】:

如果您更改 init(from 中的类型,您必须在 encode( 中执行相反的操作 不需要创建编码功能,我已经完成了编码功能,但这并不能解决保存到用户默认设置的主要问题。 确实有必要。例如,您将hasRoom 编码为Bool(使用合成编码器),但您将其解码为Int,这会导致类型不匹配。尊重,没有冒犯,但你的整个结构是一团糟。 ?????? 这里的问题是我从端点得到的,如果是布尔值 true 或 false,那会很好,但确实是布尔值 0 和 1,所以如果我这样做,那么 Int 必须对只有 0 和 1 所以很明显一个布尔值只是没有从端点恢复 尽管如此,如果您使用完整的Codable 协议,所有编码和解码类型都必须匹配。 【参考方案1】:

日期本身符合可编码协议。为什么您尝试在您的init(from decoder: Decoder) 中自己进行一些自定义解码?您不应该尝试将其解码为字符串。我只是尝试了一个像这样的非常小的实现:

//: 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()

【讨论】:

事情是生日来自端点的字符串,所以我必须对其进行格式化以制作自定义日期,否则编码器和解码器会失败。生日格式类似于“yyyy-MM-dd HH:mm:ss” 对不起,我不太明白。您在解码中使用了不同的格式:Date(fromString: String(date.dropLast(10)), format: .custom("yyyy-MM-dd"))。所以你得到了一个 JSON 对象,你只是想把它转换成一个 swift 对象? 我只是调整到从网络请求接收到的日期格式,我得到的是一个格式为“yyyy-MM-dd HH:mm:ss”的字符串,所以我做了一个 droplast(10)编码为 mmmm-YY-dd 不使用时间部分 现在我明白你的问题了。您实现了 init(from: aDecoder) 以便使用从服务器获得的 json 响应来初始化您的结构。现在您遇到了无法解码从 UserDefaults 读取的 json 的问题,因为您没有按照帖子下的 cmets 中的建议实现编码功能。这会导致生日被编码为日期,而您想在解码方法中解码字符串。 在这种情况下,覆盖解码器不是要走的路。只需在您的结构中使用编码器函数实现编码,并将您的日期编码为与您从服务器获取的格式相同的字符串。否则,您可以简单地将日期字符串一直保存在结构中,并在需要时将字符串转换为日期对象。

以上是关于如何为可编码对象提供自定义代码功能的主要内容,如果未能解决你的问题,请参考以下文章

如何为 ios 本地通知内容提供自定义辅助功能文本 - UNNotificationContent

Swift Firebase - 将估计的 ServerTimestamp 与可编码的自定义对象相结合

如何为特定来电提供自定义振动

如何为 Django 对象创建自定义过滤器标签?

如何为自定义对象的 ArrayList 添加 parcelable 实现?

如何为复杂对象编写自定义 JSON 解码器?