如何从Swift 4的Codable中排除属性


104

Swift 4的新的Encodable/Decodable协议使JSON(反)序列化变得非常令人愉快。但是,我还没有找到一种方法可以对应该编码的属性和应该解码的属性进行细粒度控制。

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


您是在说您有一些要编码的属性,但是要解码的其他属性吗?(即,您是否希望类型不是双向的?)因为如果您只想排除该属性,则为其提供默认值并将其保留在CodingKeys枚举中就足够了。
Itai Ferber

无论如何,你总是可以实现的要求Codable协议(init(from:)encode(to:)手动对过程的完全控制)。
Itai Ferber

我的特定用例是避免给解码器过多的控制权,这可能导致覆盖内部属性值而导致远程获取JSON。以下解决方案已足够!
RamwiseMatt

1
我希望看到一个答案/新的Swift功能,该功能仅需要处理特殊情况和排除的键,而不是重新实现通常应该免费获得的所有属性。
pkamb

Answers:


182

编码/解码的密钥列表由一种称为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
        case lastName
        case fullName
    }

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

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

17
您需要为此提供nickname一个默认值。否则,将无法分配任何值给属性init(from:)
Itai Ferber

1
@ItaiFerber我将其切换为可选选项,该选项最初在我的Xcode中使用
Code Different

您确定必须encode在非对称示例中提供吗?由于这仍然是标准行为,因此我认为这不是必需的。只是decode因为那是不对称的来源。
Mark A. Donohoe

1
@MarqueIV是的,您必须这样做。由于fullName无法映射到存储的属性,因此必须提供自定义的编码器和解码器。
代码不同

2

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

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


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

}

同样的方法可以用于解码器


1

您可以使用计算的属性:

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。
克里斯·H·

0

尽管可以做到这一点,但最终最终会变得非常不灵活,甚至变得不JSONy。我想我知道您来自哪里,#ids的概念在HTML中很盛行,但很少传递到JSON我认为是好东西(TM)的世界。

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


0

我已经使用了协议及其扩展名以及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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.