146

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

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

4

7 回答 7

249

要编码/解码的键列表由称为的类型控制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: )and实现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)
    }
}
于 2017-06-20T22:06:14.437 回答
6

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

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

}

相同的方法可用于解码器

于 2020-05-18T19:30:42.007 回答
4

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

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

于 2019-05-27T10:13:19.373 回答
2

您可以使用计算属性:

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
  }
}
于 2019-12-16T18:58:29.930 回答
0

虽然可以做到这一点,但它最终会变得非常unSwifty甚至unJSONy。我想我明白你来自哪里,#ids 的概念在 HTML 中很普遍,但它很少被传送到JSON我认为是一件好事(TM) 的世界。

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

于 2018-04-27T00:13:35.867 回答
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)

于 2019-07-29T09:26:26.240 回答
0

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

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
    }
}
于 2022-02-11T23:38:12.180 回答