Search code examples
rustserde

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


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.


Solution

  • 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);
    }