如何从迅速编码中排除属性?

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

我已经注意到,从伴随的 CodingKeys枚举中排除属性将完全排除属性从过程中,但是有没有更细粒度控制的方法?

55974 次浏览

要编码/解码的键列表由一个名为 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)
}
}

虽然这个 可以被做它最终结束非常 不快甚至 UnJSONY。我想我知道你们从哪里来了,#id的概念在 HTML 中很流行,但是它很少被传输到我认为是 这是好事(TM)的 JSON世界中。

如果您使用递归散列重新构造 JSON文件,一些 Codable结构将能够很好地解析它,也就是说,如果您的 recipe只包含一个 ingredients数组,而这个数组又包含(一个或多个) ingredient_info。这样,解析器将帮助您在第一个地方将您的网络缝合在一起,您只需要通过一个简单的遍历结构 如果你真的需要的话提供一些反向链接。因为这需要一个彻底的返工您的 JSON还有您的数据结构,我只是草图的想法,你认为它。如果您认为可以接受,请在评论中告诉我,然后我可以进一步详细说明,但根据情况,您可能无权更改其中任何一个。

如果我们需要从结构中的一大组属性中排除对两个属性的解码,那么将它们声明为可选属性。展开选项的代码比在 CodingKey 枚举下编写大量密钥要少。

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

我使用 protocol 及其扩展以及 AssociatedObject 来设置和获取 image (或任何需要从 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)上使用

可以使用计算属性:

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