Skip to main content

serde_patch/
apply_patch.rs

1use serde::{Serialize, de::DeserializeOwned};
2use serde_json::{Map, Value};
3
4/// Applies a JSON Merge Patch (RFC 7396) to the given value.
5///
6/// Consumes `current`, merges the patch, and returns a new updated value.
7/// Fields present in the patch replace the corresponding fields in `current`.
8/// `null` in the patch removes the field (if the target type supports it, e.g. `Option<T>`).
9/// Absent fields remain unchanged.
10///
11/// # Errors
12///
13/// Returns an error if serialization, deserialization, or patch parsing fails.
14pub fn apply_merge_patch<T>(current: T, patch_json: &str) -> Result<T, serde_json::Error>
15where
16    T: Serialize + DeserializeOwned,
17{
18    let mut current_val = serde_json::to_value(current)?;
19
20    let patch_val: Value = serde_json::from_str(patch_json)?;
21
22    merge_patch(&mut current_val, &patch_val);
23
24    serde_json::from_value(current_val)
25}
26
27/// Recursively merges a patch into a target JSON value (internal).
28fn merge_patch(target: &mut Value, patch: &Value) {
29    if let Value::Object(patch_map) = patch {
30        if !target.is_object() {
31            *target = Value::Object(Map::new());
32        }
33
34        let target_map = target.as_object_mut().unwrap();
35
36        for (key, patch_value) in patch_map {
37            if patch_value.is_null() {
38                target_map.remove(key);
39            } else {
40                let target_entry = target_map.entry(key.clone()).or_insert(Value::Null);
41                merge_patch(target_entry, patch_value);
42            }
43        }
44    } else {
45        *target = patch.clone();
46    }
47}
48
49/// Applies a JSON Merge Patch (RFC 7396).
50///
51/// Consumes the current value and returns the updated value.
52///
53/// # Example
54///
55/// ```
56/// use serde_patch::apply;
57///
58/// #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
59/// struct User { id: u32, name: String }
60///
61/// let current = User { id: 1, name: "old".to_string() };
62/// let patch = r#"{ "name": "new" }"#;
63///
64/// let updated = apply!(current, patch).unwrap();
65/// assert_eq!(updated.name, "new");
66/// assert_eq!(updated.id, 1);
67/// ```
68#[macro_export]
69macro_rules! apply {
70    ($current:expr, $patch_json:expr) => {{ $crate::apply_patch::apply_merge_patch($current, $patch_json) }};
71}