【问题标题】:How to exclude properties from Swift Codable?如何从 Swift Codable 中排除属性?
【发布时间】:2017-11-23 03:29:34
【问题描述】:

Swift 的Encodable/Decodable 协议与 Swift 4 一起发布,使 JSON(反)序列化非常愉快。但是,我还没有找到一种方法来细粒度地控制哪些属性应该被编码,哪些应该被解码。

我注意到从随附的CodingKeys 枚举中排除该属性会完全将该属性排除在进程之外,但是有没有办法进行更细粒度的控制?

【问题讨论】:

  • 你是说你有一些你想编码的属性,但你想解码不同的属性? (即您希望您的类型不是可往返的?)因为如果您只关心排除该属性,则给它一个默认值并将其排除在 CodingKeys 枚举之外就足够了。
  • 无论如何,您始终可以手动实现Codable 协议(init(from:)encode(to:))的要求,以完全控制流程。
  • 我的具体用例是避免给解码器过多的控制权,这可能导致通过覆盖内部属性值远程获取 JSON。下面的解决方案就足够了!
  • 我希望看到一个答案/新的 Swift 功能,它只需要处理特殊情况和排除的键,而不是重新实现您通常应该免费获得的所有属性。跨度>

标签: json swift codable


【解决方案1】:

要编码/解码的键列表由称为CodingKeys 的类型控制(注意末尾的s)。编译器可以为您合成它,但始终可以覆盖它。

假设您想从编码解码中排除属性nickname

struct Person: Codable {
    var firstName: String
    var lastName: String
    var nickname: String?
    
    private enum CodingKeys: String, CodingKey {
        case firstName, lastName
    }
}

如果您希望它是非对称的(即编码但不解码,反之亦然),您必须提供自己的 encode(with encoder: )init(from decoder: ) 实现:

struct Person: Codable {
    var firstName: String
    var lastName: String
    
    // Since fullName is a computed property, it's excluded by default
    var fullName: String {
        return firstName + " " + lastName
    }

    private enum CodingKeys: String, CodingKey {
        case firstName, lastName, fullName
    }

    // We don't want to decode `fullName` from the JSON
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
    }

    // But we want to store `fullName` in the JSON anyhow
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(firstName, forKey: .firstName)
        try container.encode(lastName, forKey: .lastName)
        try container.encode(fullName, forKey: .fullName)
    }
}

【讨论】:

  • 您需要给nickname 一个默认值才能使其工作。否则,将无法为 init(from:) 上的属性分配任何值。
  • @ItaiFerber 我将它切换为一个可选的,它最初在我的 Xcode 中
  • 您确定必须在非对称示例中提供encode 吗?由于这仍然是标准行为,我认为不需要它。只是decode,因为这就是不对称的来源。
  • @MarqueIV 是的,你必须这样做。由于fullName 无法映射到存储属性,因此您必须提供自定义编码器和解码器。
  • 刚刚在 Swift 5 中测试过。你应该只需要为你不解码的属性定义一个常量。您无需将密钥显式添加到CodingKeys。所以,var nickname: String { get { "name" } } 就足够了。
【解决方案2】:

另一种从编码器中排除某些属性的方法,可以使用单独的编码容器

struct Person: Codable {
    let firstName: String
    let lastName: String
    let excludedFromEncoder: String

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
    }
    private enum AdditionalCodingKeys: String, CodingKey {
        case excludedFromEncoder
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let anotherContainer = try decoder.container(keyedBy: AdditionalCodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)

        excludedFromEncoder = try anotherContainer(String.self, forKey: . excludedFromEncoder)
    }

    // it is not necessary to implement custom encoding
    // func encode(to encoder: Encoder) throws

    // let person = Person(firstName: "fname", lastName: "lname", excludedFromEncoder: "only for decoding")
    // let jsonData = try JSONEncoder().encode(person)
    // let jsonString = String(data: jsonData, encoding: .utf8)
    // jsonString --> {"firstName": "fname", "lastName": "lname"}

}

解码器也可以使用同样的方法

【讨论】:

  • 解码器在执行解码时会自动选择正确的容器还是我应该为此定义条件逻辑?
  • @Citizen_5 解码器具有同时使用 encodingkey 枚举(常规 CodingKeys 和自定义枚举)的逻辑,但编码器只知道 CodingKeys,这就是它以这种方式工作的原因。
【解决方案3】:

如果我们需要从结构中的大量属性中排除对几个属性的解码,请将它们声明为可选属性。解包选项的代码比在 CodingKey 枚举下写很多键要少。

我建议使用扩展来添加计算实例属性和计算类型属性。它将可编码的一致性属性与其他逻辑分开,因此提供了更好的可读性。

【讨论】:

    【解决方案4】:

    您可以使用计算属性:

    struct Person: Codable {
      var firstName: String
      var lastName: String
      var nickname: String?
    
      var nick: String {
        get {
          nickname ?? ""
        }
      }
    
      private enum CodingKeys: String, CodingKey {
        case firstName, lastName
      }
    }
    

    【讨论】:

    • 这是我的线索 - 使用 lazy var 有效地使其成为运行时属性将其从 Codable 中排除。
    • 计算属性不能偷懒,能不能扩展一下思路? @ChrisH
    • @BrunoMuniz 老实说不记得我为什么要发表这个评论,但我很可能指的是 lazy var x:String = { //Computed string }() 之类的东西,我用它而不是计算属性,尽管我真的无法想象为什么。
    【解决方案5】:

    虽然这可以完成,但最终会变得非常unSwifty甚至unJSONy。我想我知道你从哪里来,#ids 的概念在 HTML 中很普遍,但它很少被传送到 JSON 的世界,我认为这是一个好东西(TM )。

    如果您使用递归哈希对其进行重构,一些Codable 结构将能够很好地解析您的JSON 文件,即如果您的recipe 只包含ingredients 的数组,而ingredients 又包含(一个或多个)ingredient_info。这样,解析器将首先帮助您将网络缝合在一起,并且您只需通过简单的遍历结构提供一些反向链接如果您真的需要它们。由于这需要彻底修改您的JSON您的数据结构,我只勾勒出这个想法供您考虑。如果您认为可以接受,请在 cmets 中告诉我,然后我可以进一步详细说明,但根据具体情况,您可能无权更改其中任何一个。

    【讨论】:

      【解决方案6】:

      我已经使用协议及其扩展以及 AssociatedObject 来设置和获取图像(或任何需要从 Codable 中排除的属性)属性。

      这样我们就不必实现自己的编码器和解码器了

      这是代码,为简单起见保留相关代码:

      protocol SCAttachmentModelProtocol{
          var image:UIImage? {get set}
          var anotherProperty:Int {get set}
      }
      extension SCAttachmentModelProtocol where Self: SCAttachmentUploadRequestModel{
          var image:UIImage? {
              set{
                  //Use associated object property to set it
              }
              get{
                  //Use associated object property to get it
              }
          }
      }
      class SCAttachmentUploadRequestModel : SCAttachmentModelProtocol, Codable{
          var anotherProperty:Int
      }
      

      现在,每当我们想要访问 Image 属性时,我们都可以在对象上使用来确认协议 (SCAttachmentModelProtocol)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-02
        • 2017-10-25
        • 2019-10-10
        相关资源
        最近更新 更多