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

时间:2018-02-02 03:58:44

标签: json swift swift4 codable

说我有这样的功能:

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"

2 个答案:

答案 0 :(得分:1)

请记住,生成的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"))

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

答案 1 :(得分:0)

约束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)
}