Is there a way to use Rust's serde / serde_json to "patch" a structure?

Viewed 581

Is there a reasonably simple way to use serde/serde_json to take an existing struct and update only those fields that are present in the JSON? This basically amounts to setting a default value at runtime rather than at compile time by implementing the Default trait or a default value generator function.

This seems like a really common use case when you have a RESTful API where you want to submit updates to state, modifying only specified fields and leaving unspecified fields unchanged.

I could do it by deserializing to the dynamic Value type and then doing a big match or if/else block to update fields, but that's verbose and ugly. I'm wondering if serde has anything to handle this.

1 Answers

Serde doesn't support anything like this. If I needed to do it, I would maybe create a parallel structure where all of the fields are optional:

use serde::Deserialize;

#[derive(Deserialize)]
pub struct MyData {
    foo: String,
    bar: u64,
    wibble: bool,
}

#[derive(Deserialize)]
pub struct MyDataPatch {
    foo: Option<String>,
    bar: Option<u64>,
    wibble: Option<bool>,
}

impl MyData {
    pub fn patch(&mut self, update: MyDataPatch) {
        if let Some(foo) = update.foo {
            self.foo = foo;
        }
        if let Some(bar) = update.bar {
            self.bar = bar;
        }
        if let Some(wibble) = update.wibble {
            self.wibble = wibble;
        }
    }
}

This is more code duplication, but less runtime overhead.

If I had a lot of these, then I'd generate all of that with a macro, rather than write it by hand. You could do that with a derivable trait, something like:

trait Patch {
    type Patch; // Self with all optional fields
    fn patch(&mut self, patch: &Self::Patch);
}
Related