设置或覆盖通用编码对象的属性

Setting or overwriting properties of generic codable objects

假设我有这样的功能:

private func setObject<T:Encodable>(object:T, updater:T) 
{

        let encoder = JSONEncoder()
        do
        {
            let objectData = try encoder.encode(object)
            let updateData = try encoder.encode(updater)

示例对象所在的位置:

public struct Something : Codable
{
    public var property1:String?
    public var property2:String?
}

对象的示例实例是

object.property1 == "A"
object.property2 == "B"

和更新者:

updater.property1 == "C"
updater.property2 == nil

有没有快速创建对象的方法:

newObject.property1 == "C"
newObject.property2 == "B"

限制为 Encodable 没有提供足够的信息。您可以创建单独的协议来更新对象。

protocol Updatable {
    func update(_ updatedObject: Self) -> Self
}

public struct Something: Updatable {
    var property1: String?
    var property2: String?

    func update(_ updatedObject: Something) -> Something {
        return Something(
            property1: updatedObject.property1 ?? property1,
            property2: updatedObject.property2 ?? property2)
    }
}

func setObject<T: Updatable>(object: T, updater: T) -> T {
    return object.update(updater)
}

请记住,生成的 Something.encode(to:) 方法将调用 encoder.container(keyedBy: CodingKeys.self) 来获取容器,并将其属性编码到该容器中。所以你需要让 objectupdate 使用同一个容器来编码自己。那么 update 的属性可能会覆盖 object 的属性。另一方面,编码器可能会抛出错误。试试看吧。

为了让它们使用相同的容器,我们将创建一个包装器类型来保存对象和更新。

import Foundation

public struct Something : Codable
{
    public var property1:String?
    public var property2:String?
}

struct UpdateWrapper {
    var something: Something
    var update: Something
}

现在我们要让UpdateWrapper符合Encodable。如果我们让编译器生成一致性,它将看起来像这样:

// What the compiler would generate, but NOT what we want.

extension UpdateWrapper: Encodable {

    enum CodingKeys: String, CodingKey {
        case something
        case update
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(something, forKey: .something)
        try container.encode(update, forKey: .update)
    }

}

我们不希望这样的原因是因为这将导致 somethingupdate 从编码器中获得不同的容器,而我们希望它们进入同一个容器。因此,我们将像这样遵守 Encodable

extension UpdateWrapper: Encodable {
    func encode(to encoder: Encoder) throws {
        try something.encode(to: encoder)
        try update.encode(to: encoder)
    }
}

这样,somethingencoder各自收到相同的Encoder,具有相同的codingPath,所以他们从编码器中得到相同的容器。我们可以这样测试:

let original = Something(property1: "A", property2: "B")
let update = Something(property1: "C", property2: nil)

let encoder = JSONEncoder()
let jsonData = try! encoder.encode(UpdateWrapper(something: original, update: update))
let jsonString = String(data: jsonData, encoding: .utf8)!
print("jsonString=\(jsonString)")

let decoder = JSONDecoder()
let reconstructed = try! decoder.decode(Something.self, from: jsonData)
print("reconstructed=\(reconstructed)")

这是输出:

jsonString={"property2":"B","property1":"C"}
reconstructed=Something(property1: Optional("C"), property2: Optional("B"))

所以它确实有效。这是个好主意吗?我不知道。我不确定是否在任何地方指定了您实际上允许使用相同的密钥对同一容器进行两次编码。