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}