如何通过指定 CodingKey 的子集来对 Codable 类型进行编码?

off*_*ome 6 encoding swift

我有一些类型,我根据CodingKeys需要为其数据成员提供自定义名称。我想根据不同调用站点的要求,CodingKeys在将数据发送到服务器之前仅对类型的子集进行编码。所需的编码因调用站点而异,因此无需考虑默认实现。

示例类型(用于演示目的):

struct Account: Codable
{
    let name: String;
    var balance: Float;

    mutating func set(balance: Float)
    {
        self.balance = balance; // verifications, etc.
    }
}

struct User: Codable
{
    enum CodingKeys: String, CodingKey
    {
        case
            id = "db_id",
            name = "db_name",
            account
            // many more keys
    }

    let id: Int;
    let name: String;
    var account: Account;
    // many more data members
}
Run Code Online (Sandbox Code Playgroud)

创建实例:

var user = User(
    id: 1, name: "john", account: Account(name: "checking", balance: 10_000));
Run Code Online (Sandbox Code Playgroud)

JSONEncoder按预期使用作品;它产生以下结果:

{
  "db_id" : 1,
  "db_name" : "john",
  "account" : {
    "balance" : 10000,
    "name" : "checking"
  }
}
Run Code Online (Sandbox Code Playgroud)

我想对User类型的子集进行编码,以便将该数据发送回服务器,以便我可以更新特定的数据字段,而不是更新类型的整个属性集。模拟使用示例:

user.account.set(balance: 15_000);
let jsonEncoding = JSONEncodeSubset.of(
    user, // instance to encode
    keys: [User.CodingKeys.id, User.CodingKeys.account] // data to include
);
Run Code Online (Sandbox Code Playgroud)

生成的 JSON 看起来像这样:

{
  "db_id" : 1,
  "account" : {
    "balance" : 15000,
    "name" : "checking"
  }
}
Run Code Online (Sandbox Code Playgroud)

服务器端,我们现在拥有执行所需更新所需的确切数据。

另一个例子:有人为我们的用户输入了错误的名称,因此另一个更新请求如下所示:

user.name = "jon"; // assume the model was modified to make this mutable
let jsonEncoding = JSONEncodeSubset.of(
    user, // instance to encode
    keys: [User.CodingKeys.id, User.CodingKeys.name] // only encode id & name
);
Run Code Online (Sandbox Code Playgroud)

预期的 JSON 编码结果:

{
  "db_id" : 1,
  "name" : "jon"
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们排除了不属于更新的信息(用户帐户)。目标是优化编码以仅包含与该特定请求相关的数据。

考虑到我有大量要更新的对象,不同的调用站点更新不同的内容,我希望有一种简洁的方法来执行编码任务。

  1. 对类型数据成员的子集进行编码可提高编码性能/内存占用量。
  2. 将大量较小的编码对象发送到服务器变得更加高效。

Swift 是否为编码类型的子集提供任何此类支持CodingKeys

Joa*_*son 2

此解决方案要求您encode(to:)为所有属性编写自定义内容,但一旦完成,它应该很容易使用。

这个想法是让encode(to:)只编码给定数组中存在的那些属性/键。因此,给出上面的示例,函数将如下所示

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    for key in selectedCodingKeys {
        switch key {
        case .id:
            try container.encode(id, forKey: .id)
        case .account:
            try container.encode(account, forKey: .account)
        case .name:
            try container.encode(name, forKey: .name)
            
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

selectedCodingKeys是结构中的新属性

 var selectedCodingKeys = [CodingKeys]()
Run Code Online (Sandbox Code Playgroud)

我们还可以添加一个特定的编码函数

mutating func encode(for codingKeys: [CodingKeys]) throws -> Data {
    self.selectedCodingKeys = codingKeys
    return try JSONEncoder().encode(self)
}
Run Code Online (Sandbox Code Playgroud)

然后可以像本例中那样进行解码

var user = User(id: 1, name: "John", account: Account(name: "main", balance: 100.0))
do {
    let data1 = try user.encode(for: [.id, .account])
    let data2 = try user.encode(for: [.id, .name])
} catch {
    print(error)
}
Run Code Online (Sandbox Code Playgroud)