1use serde_json::Value;
8use std::borrow::Cow;
9
10#[derive(Debug, Clone)]
12pub enum PatchOperationRef<'a> {
13 Add {
15 path: Cow<'a, str>,
17 value: Cow<'a, Value>,
19 },
20 Remove {
22 path: Cow<'a, str>,
24 },
25 Replace {
27 path: Cow<'a, str>,
29 value: Cow<'a, Value>,
31 },
32 Move {
34 from: Cow<'a, str>,
36 path: Cow<'a, str>,
38 },
39 Copy {
41 from: Cow<'a, str>,
43 path: Cow<'a, str>,
45 },
46 Test {
48 path: Cow<'a, str>,
50 value: Cow<'a, Value>,
52 },
53}
54
55impl<'a> PatchOperationRef<'a> {
56 #[must_use]
58 pub const fn add(path: String, value: &'a Value) -> Self {
59 Self::Add {
60 path: Cow::Owned(path),
61 value: Cow::Borrowed(value),
62 }
63 }
64
65 #[must_use]
67 pub const fn remove(path: String) -> Self {
68 Self::Remove {
69 path: Cow::Owned(path),
70 }
71 }
72
73 #[must_use]
75 pub const fn replace(path: String, value: &'a Value) -> Self {
76 Self::Replace {
77 path: Cow::Owned(path),
78 value: Cow::Borrowed(value),
79 }
80 }
81
82 #[must_use]
84 pub const fn move_op(from: String, path: String) -> Self {
85 Self::Move {
86 from: Cow::Owned(from),
87 path: Cow::Owned(path),
88 }
89 }
90
91 #[must_use]
93 pub const fn copy(from: String, path: String) -> Self {
94 Self::Copy {
95 from: Cow::Owned(from),
96 path: Cow::Owned(path),
97 }
98 }
99
100 #[must_use]
102 pub const fn test(path: String, value: &'a Value) -> Self {
103 Self::Test {
104 path: Cow::Owned(path),
105 value: Cow::Borrowed(value),
106 }
107 }
108
109 #[must_use]
111 pub const fn op_type(&self) -> &'static str {
112 match self {
113 Self::Add { .. } => "add",
114 Self::Remove { .. } => "remove",
115 Self::Replace { .. } => "replace",
116 Self::Move { .. } => "move",
117 Self::Copy { .. } => "copy",
118 Self::Test { .. } => "test",
119 }
120 }
121
122 #[must_use]
124 pub fn path(&self) -> &str {
125 match self {
126 Self::Add { path, .. }
127 | Self::Remove { path }
128 | Self::Replace { path, .. }
129 | Self::Move { path, .. }
130 | Self::Copy { path, .. }
131 | Self::Test { path, .. } => path,
132 }
133 }
134
135 #[must_use]
137 pub fn into_owned(self) -> PatchOperationRef<'static> {
138 match self {
139 Self::Add { path, value } => PatchOperationRef::Add {
140 path: Cow::Owned(path.into_owned()),
141 value: Cow::Owned(value.into_owned()),
142 },
143 Self::Remove { path } => PatchOperationRef::Remove {
144 path: Cow::Owned(path.into_owned()),
145 },
146 Self::Replace { path, value } => PatchOperationRef::Replace {
147 path: Cow::Owned(path.into_owned()),
148 value: Cow::Owned(value.into_owned()),
149 },
150 Self::Move { from, path } => PatchOperationRef::Move {
151 from: Cow::Owned(from.into_owned()),
152 path: Cow::Owned(path.into_owned()),
153 },
154 Self::Copy { from, path } => PatchOperationRef::Copy {
155 from: Cow::Owned(from.into_owned()),
156 path: Cow::Owned(path.into_owned()),
157 },
158 Self::Test { path, value } => PatchOperationRef::Test {
159 path: Cow::Owned(path.into_owned()),
160 value: Cow::Owned(value.into_owned()),
161 },
162 }
163 }
164}
165
166#[derive(Debug, Default, Clone)]
168pub struct JsonPatchRef<'a> {
169 pub operations: Vec<PatchOperationRef<'a>>,
171}
172
173impl<'a> JsonPatchRef<'a> {
174 #[must_use]
176 pub const fn new() -> Self {
177 Self {
178 operations: Vec::new(),
179 }
180 }
181
182 #[must_use]
184 pub fn with_capacity(capacity: usize) -> Self {
185 Self {
186 operations: Vec::with_capacity(capacity),
187 }
188 }
189
190 pub fn push(&mut self, op: PatchOperationRef<'a>) {
192 self.operations.push(op);
193 }
194
195 #[must_use]
197 pub const fn len(&self) -> usize {
198 self.operations.len()
199 }
200
201 #[must_use]
203 pub const fn is_empty(&self) -> bool {
204 self.operations.is_empty()
205 }
206
207 pub fn iter(&self) -> impl Iterator<Item = &PatchOperationRef<'a>> {
209 self.operations.iter()
210 }
211
212 #[must_use]
214 pub fn into_owned(self) -> JsonPatchRef<'static> {
215 JsonPatchRef {
216 operations: self
217 .operations
218 .into_iter()
219 .map(PatchOperationRef::into_owned)
220 .collect(),
221 }
222 }
223
224 #[must_use]
226 pub fn to_json_patch(&self) -> super::patch::JsonPatch {
227 use super::patch::PatchOperation;
228
229 let ops: Vec<PatchOperation> = self
230 .operations
231 .iter()
232 .map(|op| match op {
233 PatchOperationRef::Add { path, value } => PatchOperation::Add {
234 path: path.to_string(),
235 value: value.as_ref().clone(),
236 },
237 PatchOperationRef::Remove { path } => PatchOperation::Remove {
238 path: path.to_string(),
239 },
240 PatchOperationRef::Replace { path, value } => PatchOperation::Replace {
241 path: path.to_string(),
242 value: value.as_ref().clone(),
243 },
244 PatchOperationRef::Move { from, path } => PatchOperation::Move {
245 from: from.to_string(),
246 path: path.to_string(),
247 },
248 PatchOperationRef::Copy { from, path } => PatchOperation::Copy {
249 from: from.to_string(),
250 path: path.to_string(),
251 },
252 PatchOperationRef::Test { path, value } => PatchOperation::Test {
253 path: path.to_string(),
254 value: value.as_ref().clone(),
255 },
256 })
257 .collect();
258
259 super::patch::JsonPatch { operations: ops }
260 }
261}
262
263impl<'a> IntoIterator for JsonPatchRef<'a> {
264 type Item = PatchOperationRef<'a>;
265 type IntoIter = std::vec::IntoIter<PatchOperationRef<'a>>;
266
267 fn into_iter(self) -> Self::IntoIter {
268 self.operations.into_iter()
269 }
270}
271
272impl<'a, 'b> IntoIterator for &'b JsonPatchRef<'a> {
273 type Item = &'b PatchOperationRef<'a>;
274 type IntoIter = std::slice::Iter<'b, PatchOperationRef<'a>>;
275
276 fn into_iter(self) -> Self::IntoIter {
277 self.operations.iter()
278 }
279}
280
281#[must_use]
293pub fn json_diff_zerocopy<'a>(source: &Value, target: &'a Value) -> JsonPatchRef<'a> {
294 let mut patch = JsonPatchRef::with_capacity(8);
295 diff_values_zerocopy(source, target, String::new(), &mut patch);
296 patch
297}
298
299#[allow(clippy::similar_names)]
301fn diff_values_zerocopy<'a>(
302 source: &Value,
303 target: &'a Value,
304 path: String,
305 patch: &mut JsonPatchRef<'a>,
306) {
307 if source == target {
309 return;
310 }
311
312 match (source, target) {
313 (Value::Object(src_map), Value::Object(tgt_map)) => {
315 for key in src_map.keys() {
317 if !tgt_map.contains_key(key) {
318 let key_path = if path.is_empty() {
319 format!("/{}", escape_json_pointer(key))
320 } else {
321 format!("{}/{}", path, escape_json_pointer(key))
322 };
323 patch.push(PatchOperationRef::remove(key_path));
324 }
325 }
326
327 for (key, tgt_val) in tgt_map {
329 let key_path = if path.is_empty() {
330 format!("/{}", escape_json_pointer(key))
331 } else {
332 format!("{}/{}", path, escape_json_pointer(key))
333 };
334
335 match src_map.get(key) {
336 Some(src_val) => {
337 diff_values_zerocopy(src_val, tgt_val, key_path, patch);
339 }
340 None => {
341 patch.push(PatchOperationRef::add(key_path, tgt_val));
343 }
344 }
345 }
346 }
347
348 (Value::Array(src_arr), Value::Array(tgt_arr)) => {
350 let src_len = src_arr.len();
351 let tgt_len = tgt_arr.len();
352
353 let min_len = src_len.min(tgt_len);
355 for i in 0..min_len {
356 let idx_path = if path.is_empty() {
357 format!("/{i}")
358 } else {
359 format!("{path}/{i}")
360 };
361 diff_values_zerocopy(&src_arr[i], &tgt_arr[i], idx_path, patch);
362 }
363
364 if tgt_len > src_len {
366 for (i, item) in tgt_arr.iter().enumerate().take(tgt_len).skip(src_len) {
368 let idx_path = if path.is_empty() {
369 format!("/{i}")
370 } else {
371 format!("{path}/{i}")
372 };
373 patch.push(PatchOperationRef::add(idx_path, item));
374 }
375 } else if src_len > tgt_len {
376 for i in (tgt_len..src_len).rev() {
378 let idx_path = if path.is_empty() {
379 format!("/{i}")
380 } else {
381 format!("{path}/{i}")
382 };
383 patch.push(PatchOperationRef::remove(idx_path));
384 }
385 }
386 }
387
388 _ => {
390 if path.is_empty() {
391 patch.push(PatchOperationRef::replace(String::new(), target));
393 } else {
394 patch.push(PatchOperationRef::replace(path, target));
395 }
396 }
397 }
398}
399
400fn escape_json_pointer(s: &str) -> Cow<'_, str> {
402 if s.contains('~') || s.contains('/') {
403 let escaped = s.replace('~', "~0").replace('/', "~1");
404 Cow::Owned(escaped)
405 } else {
406 Cow::Borrowed(s)
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use serde_json::json;
414
415 #[test]
420 fn test_patch_operation_add() {
421 let value = json!(42);
422 let op = PatchOperationRef::add("/path".to_string(), &value);
423 assert_eq!(op.op_type(), "add");
424 assert_eq!(op.path(), "/path");
425 }
426
427 #[test]
428 fn test_patch_operation_remove() {
429 let op = PatchOperationRef::remove("/path".to_string());
430 assert_eq!(op.op_type(), "remove");
431 assert_eq!(op.path(), "/path");
432 }
433
434 #[test]
435 fn test_patch_operation_replace() {
436 let value = json!("new");
437 let op = PatchOperationRef::replace("/path".to_string(), &value);
438 assert_eq!(op.op_type(), "replace");
439 assert_eq!(op.path(), "/path");
440 }
441
442 #[test]
443 fn test_patch_operation_move() {
444 let op = PatchOperationRef::move_op("/from".to_string(), "/to".to_string());
445 assert_eq!(op.op_type(), "move");
446 assert_eq!(op.path(), "/to");
447 }
448
449 #[test]
450 fn test_patch_operation_copy() {
451 let op = PatchOperationRef::copy("/from".to_string(), "/to".to_string());
452 assert_eq!(op.op_type(), "copy");
453 assert_eq!(op.path(), "/to");
454 }
455
456 #[test]
457 fn test_patch_operation_test() {
458 let value = json!(true);
459 let op = PatchOperationRef::test("/path".to_string(), &value);
460 assert_eq!(op.op_type(), "test");
461 assert_eq!(op.path(), "/path");
462 }
463
464 #[test]
469 fn test_patch_operation_into_owned_add() {
470 let value = json!(42);
471 let op = PatchOperationRef::add("/path".to_string(), &value);
472 let owned = op.into_owned();
473 assert_eq!(owned.op_type(), "add");
474 assert_eq!(owned.path(), "/path");
475 }
476
477 #[test]
478 fn test_patch_operation_into_owned_remove() {
479 let op = PatchOperationRef::remove("/path".to_string());
480 let owned = op.into_owned();
481 assert_eq!(owned.op_type(), "remove");
482 assert_eq!(owned.path(), "/path");
483 }
484
485 #[test]
486 fn test_patch_operation_into_owned_replace() {
487 let value = json!("new");
488 let op = PatchOperationRef::replace("/path".to_string(), &value);
489 let owned = op.into_owned();
490 assert_eq!(owned.op_type(), "replace");
491 }
492
493 #[test]
494 fn test_patch_operation_into_owned_move() {
495 let op = PatchOperationRef::move_op("/from".to_string(), "/to".to_string());
496 let owned = op.into_owned();
497 assert_eq!(owned.op_type(), "move");
498 }
499
500 #[test]
501 fn test_patch_operation_into_owned_copy() {
502 let op = PatchOperationRef::copy("/from".to_string(), "/to".to_string());
503 let owned = op.into_owned();
504 assert_eq!(owned.op_type(), "copy");
505 }
506
507 #[test]
508 fn test_patch_operation_into_owned_test() {
509 let value = json!(true);
510 let op = PatchOperationRef::test("/path".to_string(), &value);
511 let owned = op.into_owned();
512 assert_eq!(owned.op_type(), "test");
513 }
514
515 #[test]
516 fn test_patch_operation_debug() {
517 let value = json!(42);
518 let op = PatchOperationRef::add("/path".to_string(), &value);
519 let debug_str = format!("{op:?}");
520 assert!(debug_str.contains("Add"));
521 }
522
523 #[test]
524 fn test_patch_operation_clone() {
525 let value = json!(42);
526 let op = PatchOperationRef::add("/path".to_string(), &value);
527 let cloned = op.clone();
528 assert_eq!(cloned.path(), "/path");
529 }
530
531 #[test]
536 fn test_json_patch_ref_new() {
537 let patch = JsonPatchRef::new();
538 assert!(patch.is_empty());
539 assert_eq!(patch.len(), 0);
540 }
541
542 #[test]
543 fn test_json_patch_ref_with_capacity() {
544 let patch = JsonPatchRef::with_capacity(10);
545 assert!(patch.is_empty());
546 }
547
548 #[test]
549 fn test_json_patch_ref_push() {
550 let value = json!(42);
551 let mut patch = JsonPatchRef::new();
552 patch.push(PatchOperationRef::add("/a".to_string(), &value));
553 assert_eq!(patch.len(), 1);
554 }
555
556 #[test]
557 fn test_json_patch_ref_iter() {
558 let value = json!(42);
559 let mut patch = JsonPatchRef::new();
560 patch.push(PatchOperationRef::add("/a".to_string(), &value));
561 patch.push(PatchOperationRef::remove("/b".to_string()));
562
563 let paths: Vec<_> = patch.iter().map(super::PatchOperationRef::path).collect();
564 assert_eq!(paths.len(), 2);
565 assert_eq!(paths[0], "/a");
566 assert_eq!(paths[1], "/b");
567 }
568
569 #[test]
570 fn test_json_patch_ref_into_iter_owned() {
571 let value = json!(42);
572 let mut patch = JsonPatchRef::new();
573 patch.push(PatchOperationRef::add("/a".to_string(), &value));
574
575 let ops_count = patch.into_iter().count();
576 assert_eq!(ops_count, 1);
577 }
578
579 #[test]
580 fn test_json_patch_ref_into_iter_ref() {
581 let value = json!(42);
582 let mut patch = JsonPatchRef::new();
583 patch.push(PatchOperationRef::add("/a".to_string(), &value));
584
585 let ops_count = (&patch).into_iter().count();
586 assert_eq!(ops_count, 1);
587 }
588
589 #[test]
590 fn test_json_patch_ref_default() {
591 let patch = JsonPatchRef::default();
592 assert!(patch.is_empty());
593 }
594
595 #[test]
596 fn test_json_patch_ref_debug() {
597 let patch = JsonPatchRef::new();
598 let debug_str = format!("{patch:?}");
599 assert!(debug_str.contains("JsonPatchRef"));
600 }
601
602 #[test]
603 fn test_json_patch_ref_clone() {
604 let value = json!(42);
605 let mut patch = JsonPatchRef::new();
606 patch.push(PatchOperationRef::add("/a".to_string(), &value));
607 let cloned = patch.clone();
608 assert_eq!(cloned.len(), 1);
609 }
610
611 #[test]
616 fn test_to_json_patch_add() {
617 let value = json!(42);
618 let mut patch = JsonPatchRef::new();
619 patch.push(PatchOperationRef::add("/a".to_string(), &value));
620 let json_patch = patch.to_json_patch();
621 assert_eq!(json_patch.operations.len(), 1);
622 }
623
624 #[test]
625 fn test_to_json_patch_remove() {
626 let mut patch = JsonPatchRef::new();
627 patch.push(PatchOperationRef::remove("/a".to_string()));
628 let json_patch = patch.to_json_patch();
629 assert_eq!(json_patch.operations.len(), 1);
630 }
631
632 #[test]
633 fn test_to_json_patch_replace() {
634 let value = json!("new");
635 let mut patch = JsonPatchRef::new();
636 patch.push(PatchOperationRef::replace("/a".to_string(), &value));
637 let json_patch = patch.to_json_patch();
638 assert_eq!(json_patch.operations.len(), 1);
639 }
640
641 #[test]
642 fn test_to_json_patch_move() {
643 let mut patch = JsonPatchRef::new();
644 patch.push(PatchOperationRef::move_op(
645 "/from".to_string(),
646 "/to".to_string(),
647 ));
648 let json_patch = patch.to_json_patch();
649 assert_eq!(json_patch.operations.len(), 1);
650 }
651
652 #[test]
653 fn test_to_json_patch_copy() {
654 let mut patch = JsonPatchRef::new();
655 patch.push(PatchOperationRef::copy(
656 "/from".to_string(),
657 "/to".to_string(),
658 ));
659 let json_patch = patch.to_json_patch();
660 assert_eq!(json_patch.operations.len(), 1);
661 }
662
663 #[test]
664 fn test_to_json_patch_test() {
665 let value = json!(true);
666 let mut patch = JsonPatchRef::new();
667 patch.push(PatchOperationRef::test("/a".to_string(), &value));
668 let json_patch = patch.to_json_patch();
669 assert_eq!(json_patch.operations.len(), 1);
670 }
671
672 #[test]
677 fn test_empty_diff() {
678 let doc = json!({"a": 1});
679 let patch = json_diff_zerocopy(&doc, &doc);
680 assert!(patch.is_empty());
681 }
682
683 #[test]
684 fn test_add_field() {
685 let source = json!({});
686 let target = json!({"name": "Alice"});
687 let patch = json_diff_zerocopy(&source, &target);
688
689 assert_eq!(patch.len(), 1);
690 assert_eq!(patch.operations[0].op_type(), "add");
691 assert_eq!(patch.operations[0].path(), "/name");
692 }
693
694 #[test]
695 fn test_remove_field() {
696 let source = json!({"name": "Alice"});
697 let target = json!({});
698 let patch = json_diff_zerocopy(&source, &target);
699
700 assert_eq!(patch.len(), 1);
701 assert_eq!(patch.operations[0].op_type(), "remove");
702 }
703
704 #[test]
705 fn test_replace_value() {
706 let source = json!({"name": "Alice"});
707 let target = json!({"name": "Bob"});
708 let patch = json_diff_zerocopy(&source, &target);
709
710 assert_eq!(patch.len(), 1);
711 assert_eq!(patch.operations[0].op_type(), "replace");
712 }
713
714 #[test]
715 fn test_nested_diff() {
716 let source = json!({"user": {"name": "Alice"}});
717 let target = json!({"user": {"name": "Bob"}});
718 let patch = json_diff_zerocopy(&source, &target);
719
720 assert_eq!(patch.len(), 1);
721 assert_eq!(patch.operations[0].path(), "/user/name");
722 }
723
724 #[test]
725 fn test_array_diff_add() {
726 let source = json!([1, 2, 3]);
727 let target = json!([1, 2, 3, 4]);
728 let patch = json_diff_zerocopy(&source, &target);
729
730 assert_eq!(patch.len(), 1);
731 assert_eq!(patch.operations[0].op_type(), "add");
732 }
733
734 #[test]
735 fn test_array_diff_remove() {
736 let source = json!([1, 2, 3, 4]);
737 let target = json!([1, 2]);
738 let patch = json_diff_zerocopy(&source, &target);
739
740 assert_eq!(patch.len(), 2);
742 assert_eq!(patch.operations[0].op_type(), "remove");
743 assert_eq!(patch.operations[1].op_type(), "remove");
744 }
745
746 #[test]
747 fn test_array_diff_change() {
748 let source = json!([1, 2, 3]);
749 let target = json!([1, 99, 3]);
750 let patch = json_diff_zerocopy(&source, &target);
751
752 assert_eq!(patch.len(), 1);
753 assert_eq!(patch.operations[0].path(), "/1");
754 assert_eq!(patch.operations[0].op_type(), "replace");
755 }
756
757 #[test]
758 fn test_type_change() {
759 let source = json!({"x": 1});
760 let target = json!({"x": "one"});
761 let patch = json_diff_zerocopy(&source, &target);
762
763 assert_eq!(patch.len(), 1);
764 assert_eq!(patch.operations[0].op_type(), "replace");
765 }
766
767 #[test]
768 fn test_root_replacement() {
769 let source = json!(42);
770 let target = json!("hello");
771 let patch = json_diff_zerocopy(&source, &target);
772
773 assert_eq!(patch.len(), 1);
774 assert_eq!(patch.operations[0].op_type(), "replace");
775 assert_eq!(patch.operations[0].path(), ""); }
777
778 #[test]
779 fn test_nested_array_in_object() {
780 let source = json!({"items": [1, 2]});
781 let target = json!({"items": [1, 2, 3]});
782 let patch = json_diff_zerocopy(&source, &target);
783
784 assert_eq!(patch.len(), 1);
785 assert_eq!(patch.operations[0].path(), "/items/2");
786 }
787
788 #[test]
789 fn test_into_owned() {
790 let source = json!({});
791 let target = json!({"x": 1});
792 let patch = json_diff_zerocopy(&source, &target);
793 let owned: JsonPatchRef<'static> = patch.into_owned();
794
795 assert!(!owned.is_empty());
796 }
797
798 #[test]
799 fn test_to_json_patch() {
800 let source = json!({});
801 let target = json!({"x": 1});
802 let patch_ref = json_diff_zerocopy(&source, &target);
803 let patch = patch_ref.to_json_patch();
804
805 assert_eq!(patch.operations.len(), 1);
806 }
807
808 #[test]
809 fn test_escape_json_pointer() {
810 assert_eq!(escape_json_pointer("simple"), "simple");
811 assert_eq!(escape_json_pointer("has/slash"), "has~1slash");
812 assert_eq!(escape_json_pointer("has~tilde"), "has~0tilde");
813 assert_eq!(escape_json_pointer("both~and/"), "both~0and~1");
814 }
815
816 #[test]
817 fn test_diff_with_escaped_keys() {
818 let source = json!({});
819 let target = json!({"a/b": 1, "c~d": 2});
820 let patch = json_diff_zerocopy(&source, &target);
821
822 assert_eq!(patch.len(), 2);
823 let paths: Vec<_> = patch.iter().map(super::PatchOperationRef::path).collect();
825 assert!(paths.contains(&"/a~1b") || paths.contains(&"/c~0d"));
826 }
827
828 #[test]
829 fn test_deeply_nested_diff() {
830 let source = json!({"a": {"b": {"c": {"d": 1}}}});
831 let target = json!({"a": {"b": {"c": {"d": 2}}}});
832 let patch = json_diff_zerocopy(&source, &target);
833
834 assert_eq!(patch.len(), 1);
835 assert_eq!(patch.operations[0].path(), "/a/b/c/d");
836 }
837
838 #[test]
839 fn test_empty_array_to_non_empty() {
840 let source = json!([]);
841 let target = json!([1, 2, 3]);
842 let patch = json_diff_zerocopy(&source, &target);
843
844 assert_eq!(patch.len(), 3);
845 }
846
847 #[test]
848 fn test_non_empty_array_to_empty() {
849 let source = json!([1, 2, 3]);
850 let target = json!([]);
851 let patch = json_diff_zerocopy(&source, &target);
852
853 assert_eq!(patch.len(), 3);
854 for op in patch.iter() {
856 assert_eq!(op.op_type(), "remove");
857 }
858 }
859
860 #[test]
861 fn test_object_to_array_type_change() {
862 let source = json!({"a": 1});
863 let target = json!([1]);
864 let patch = json_diff_zerocopy(&source, &target);
865
866 assert_eq!(patch.len(), 1);
867 assert_eq!(patch.operations[0].op_type(), "replace");
868 assert_eq!(patch.operations[0].path(), "");
869 }
870
871 #[test]
872 fn test_nested_object_add() {
873 let source = json!({"user": {}});
874 let target = json!({"user": {"name": "Alice"}});
875 let patch = json_diff_zerocopy(&source, &target);
876
877 assert_eq!(patch.len(), 1);
878 assert_eq!(patch.operations[0].op_type(), "add");
879 assert_eq!(patch.operations[0].path(), "/user/name");
880 }
881
882 #[test]
883 fn test_nested_object_remove() {
884 let source = json!({"user": {"name": "Alice", "age": 30}});
885 let target = json!({"user": {"name": "Alice"}});
886 let patch = json_diff_zerocopy(&source, &target);
887
888 assert_eq!(patch.len(), 1);
889 assert_eq!(patch.operations[0].op_type(), "remove");
890 assert_eq!(patch.operations[0].path(), "/user/age");
891 }
892}