如何从 Swift Codable 中排除属性?

How to exclude properties from Swift Codable?

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

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

encode/decode 的键列表由称为 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)
    }
}

虽然这 可以 完成,但它最终会非常 unSwifty 甚至 unJSONy .我想我知道你是从哪里来的,#ids 的概念在 HTML 中很流行,但很少被传送到 JSON 的世界,我认为 [=22] =]好东西(商标)。

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

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

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

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

现在,无论何时我们想要访问图像 属性 我们都可以在对象上使用确认协议 (SCAttachmentModelProtocol)

您可以使用计算属性:

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

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

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

}

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

使用自定义 属性 包装器的解决方案

struct Person: Codable {
    var firstName: String
    var lastName: String
    
    @CodableIgnored
    var nickname: String?
}

其中CodableIgnored

@propertyWrapper
public struct CodableIgnored<T>: Codable {
    public var wrappedValue: T?
        
    public init(wrappedValue: T?) {
        self.wrappedValue = wrappedValue
    }
    
    public init(from decoder: Decoder) throws {
        self.wrappedValue = nil
    }
    
    public func encode(to encoder: Encoder) throws {
        // Do nothing
    }
}

extension KeyedDecodingContainer {
    public func decode<T>(
        _ type: CodableIgnored<T>.Type,
        forKey key: Self.Key) throws -> CodableIgnored<T>
    {
        return CodableIgnored(wrappedValue: nil)
    }
}

extension KeyedEncodingContainer {
    public mutating func encode<T>(
        _ value: CodableIgnored<T>,
        forKey key: KeyedEncodingContainer<K>.Key) throws
    {
        // Do nothing
    }
}