如何从 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 .我想我知道你是从哪里来的,#id
s 的概念在 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
}
}
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 .我想我知道你是从哪里来的,#id
s 的概念在 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
}
}