Swift 4发布的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: )
和 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)
}
}
nickname
设置一个默认值。否则,在 init(from:)
中无法为该属性分配任何值。 - Itai FerberfullName
无法映射到存储属性,所以你必须提供自定义的编码器和解码器。 - Code DifferentCodingKeys
中。因此,var nickname: String { get { "name" } }
应该就足够了。 - Leo使用自定义属性包装器的解决方案
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
}
}
KeyedDecodingContainer
和 KeyedEncodingContainer
的扩展真的有必要吗?在我的情况下,没有它们编码和解码也能正常工作。 - Nickkk另一种排除某些属性不被编码器编码的方法是使用单独的编码容器。
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
var nickname: String?
var nick: String {
get {
nickname ?? ""
}
}
private enum CodingKeys: String, CodingKey {
case firstName, lastName
}
}
lazy var
有效地将其变成运行时属性,从而将其排除在可编码范围之外。 - ChrisHlazy var x:String = { //Computed string }()
这样的东西,它可以代替计算属性,尽管我真的想象不出来为什么要这样做。 - ChrisH虽然这是可以做到的,但最终结果会变得非常不符合 Swift 的风格,甚至不符合 JSON 的规范。我明白你的想法,因为在 HTML 中,#id
是很常见的概念,但它很少被用于 JSON 中,这是我认为的一件好事。
如果您使用递归哈希重构您的 JSON 文件,即如果您的 recipe
只包含一个 ingredients
数组,该数组又包含(一个或多个)ingredient_info
,那么某些 Codable
结构体将能够很好地解析您的 JSON 文件。这样,解析器将帮助您首先将网络拼接在一起,而您只需要通过简单遍历结构来提供一些反向链接,如果您真的需要它们的话。由于这需要对您的 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
}
CodingKeys
枚举中省略即可。 - Itai FerberCodable
协议的要求(init(from:)
和encode(to:)
),以完全控制该过程。 - Itai Ferber