与 Swift 4 一起发布的Swift 的Encodable
/Decodable
协议使 JSON(反)序列化非常愉快。但是,我还没有找到一种方法来细粒度地控制哪些属性应该被编码,哪些应该被解码。
我注意到从随附的CodingKeys
枚举中排除该属性会完全将该属性排除在进程之外,但是有没有办法进行更细粒度的控制?
要编码/解码的键列表由称为的类型控制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)
}
}
另一种从编码器中排除某些属性的方法,可以使用单独的编码容器
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"}
}
相同的方法可用于解码器
如果我们需要从结构中的大量属性中排除对几个属性的解码,请将它们声明为可选属性。解包选项的代码少于在 CodingKey 枚举下编写大量键。
我建议使用扩展来添加计算实例属性和计算类型属性。它将可编码的一致性属性与其他逻辑分开,因此提供了更好的可读性。
您可以使用计算属性:
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
}
}
虽然可以做到这一点,但它最终会变得非常unSwifty甚至unJSONy。我想我明白你来自哪里,#id
s 的概念在 HTML 中很普遍,但它很少被传送到JSON
我认为是一件好事(TM) 的世界。
如果您使用递归哈希对其进行重构,则某些Codable
结构将能够JSON
很好地解析您的文件,即如果您recipe
只包含一个数组,ingredients
而该数组又包含 (一个或多个) ingredient_info
。这样,解析器将首先帮助您将网络缝合在一起,如果您真的需要它们,您只需通过简单的结构遍历提供一些反向链接。由于这需要对您JSON
和您的数据结构进行彻底的修改,我只勾勒出这个想法供您考虑。如果您认为可以接受,请在评论中告诉我,然后我可以进一步详细说明,但根据具体情况,您可能无权更改其中任何一个。
我已经使用协议及其扩展以及 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)
使用自定义属性包装器的解决方案
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
}
}