Partial encoding for PATCH updates

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.

¯\_(ツ)_/¯

1 Like

I guess you'd have to perform KeyPath -> CodingPath translations... You may have a look at this method from vapor/core, which is the most robust hack out there in the wild that I'm aware of.

1 Like

Thank you! The longer I think about it, the more I’m convinced it doesn’t actually make sense apart from the simplest case (using a separate type), since I would be assuming way too much about the encoded structure. I would be quite interested in a well-typed Swift REST framework that would handle this in a disciplined way.