I have a big Codable object for interacting with a REST API:
struct User: Codable {
var name: String
var age: Int
// many other fields
}
Now I am trying to design a decent way to encode changes to this object as JSON for a PATCH request. The catch is that I need the encoded JSON to only contain the fields that I want to change. What I have tried so far:
Using a Special Object
struct PartialUser: Codable {
var name: String?
var age: Int?
// many other fields repeated, all optional
}
var user = PartialUser()
user.name = "Foo"
user.age = 42
let data = try JSONEncoder().encode(user) // {"name": "Foo", "age": 42}
This is simple, but I would hate to keep those two types (Foo, PartialFoo) in sync manually for every patchable object in the API.
Delta Updates
let user = /* receive previous user value */
var updatedUser = user
updatedUser.name = "Foo"
updatedUser.age = 42
let data = try JSONEncoder().encode(updatedUser.changes(since: user))
Where changes(since:) is a custom extension on Encodable that returns a generic JSON enum. This works nicely, until it breaks down when the user value is stale, ie. when the age stored in user is already 42, but a different number is stored on the server. (The delta in that case is no-op for age, which is wrong.)
Key Paths
public struct PatchProxy<Base>: Encodable {
public func set<Val>(path: KeyPath<Base, Val>, to value: Val) {
// mark field + value to encode (or filter?) it later
}
}
var user = PatchProxy<User>()
user.set(\.name, "Foo")
user.set(\.age, 42)
let data = try JSONEncoder().encode(user)
But here I have no idea how to do the partial encoding or filtering.
Post-Encoding Filter
I imagined something like this could also work:
extension Encodable {
func patch(includedFields: Set<Property>) -> JSON {
var json = JSON(encodable: self)
/* now filter encoded JSON according to `includedFields` */
}
}
The basic idea is a bit like in the attempt with key paths, but using CodingKeys or a custom hand-written enum to say which properties should be included. JSON is the generic encodable structure mentioned before, but I don’t know what Property should be – the compiler-supplied CodingKeys are private.
¯\_(ツ)_/¯