1use super::patch::{JsonPatch, PatchOperation};
8use super::simd_compare::simd_bytes_equal;
9use serde_json::Value;
10
11#[derive(Debug, Clone, Default)]
13pub struct DiffOptions {
14 pub detect_moves: bool,
16 pub detect_copies: bool,
18 pub optimize_arrays: bool,
20}
21
22impl DiffOptions {
23 #[must_use]
25 pub const fn with_moves(mut self) -> Self {
26 self.detect_moves = true;
27 self
28 }
29
30 #[must_use]
32 pub const fn with_copies(mut self) -> Self {
33 self.detect_copies = true;
34 self
35 }
36
37 #[must_use]
39 pub const fn with_array_optimization(mut self) -> Self {
40 self.optimize_arrays = true;
41 self
42 }
43}
44
45#[must_use]
56pub fn json_diff(source: &Value, target: &Value) -> JsonPatch {
57 json_diff_with_options(source, target, &DiffOptions::default())
58}
59
60#[must_use]
62pub fn json_diff_with_options(source: &Value, target: &Value, options: &DiffOptions) -> JsonPatch {
63 let mut patch = JsonPatch::new();
64 diff_values(source, target, "", &mut patch, options);
65 patch
66}
67
68fn diff_values(
70 source: &Value,
71 target: &Value,
72 path: &str,
73 ops: &mut JsonPatch,
74 options: &DiffOptions,
75) {
76 if values_equal_fast(source, target) {
78 return;
79 }
80
81 match (source, target) {
82 (Value::Object(src_map), Value::Object(tgt_map)) => {
84 diff_objects(src_map, tgt_map, path, ops, options);
85 }
86
87 (Value::Array(src_arr), Value::Array(tgt_arr)) => {
89 if options.optimize_arrays {
90 diff_arrays_optimized(src_arr, tgt_arr, path, ops, options);
91 } else {
92 diff_arrays_simple(src_arr, tgt_arr, path, ops, options);
93 }
94 }
95
96 _ => {
98 ops.push(PatchOperation::Replace {
99 path: path.to_string(),
100 value: target.clone(),
101 });
102 }
103 }
104}
105
106fn values_equal_fast(a: &Value, b: &Value) -> bool {
108 match (a, b) {
109 (Value::Null, Value::Null) => true,
110 (Value::Bool(a), Value::Bool(b)) => a == b,
111 (Value::Number(a), Value::Number(b)) => a == b,
112 (Value::String(a), Value::String(b)) => {
113 simd_bytes_equal(a.as_bytes(), b.as_bytes())
115 }
116 (Value::Array(a), Value::Array(b)) => {
117 if a.len() != b.len() {
118 return false;
119 }
120 a.iter().zip(b.iter()).all(|(x, y)| values_equal_fast(x, y))
121 }
122 (Value::Object(a), Value::Object(b)) => {
123 if a.len() != b.len() {
124 return false;
125 }
126 a.iter()
127 .all(|(k, v)| b.get(k).is_some_and(|bv| values_equal_fast(v, bv)))
128 }
129 _ => false,
130 }
131}
132
133fn diff_objects(
135 source: &serde_json::Map<String, Value>,
136 target: &serde_json::Map<String, Value>,
137 path: &str,
138 ops: &mut JsonPatch,
139 options: &DiffOptions,
140) {
141 for key in source.keys() {
143 if !target.contains_key(key) {
144 let key_path = format_path(path, key);
145 ops.push(PatchOperation::Remove { path: key_path });
146 }
147 }
148
149 for (key, tgt_value) in target {
151 let key_path = format_path(path, key);
152
153 match source.get(key) {
154 Some(src_value) => {
155 diff_values(src_value, tgt_value, &key_path, ops, options);
157 }
158 None => {
159 ops.push(PatchOperation::Add {
161 path: key_path,
162 value: tgt_value.clone(),
163 });
164 }
165 }
166 }
167}
168
169fn diff_arrays_simple(
171 source: &[Value],
172 target: &[Value],
173 path: &str,
174 ops: &mut JsonPatch,
175 options: &DiffOptions,
176) {
177 let src_len = source.len();
178 let tgt_len = target.len();
179
180 let common_len = src_len.min(tgt_len);
182 for i in 0..common_len {
183 let elem_path = format!("{path}/{i}");
184 diff_values(&source[i], &target[i], &elem_path, ops, options);
185 }
186
187 for i in (tgt_len..src_len).rev() {
189 ops.push(PatchOperation::Remove {
190 path: format!("{path}/{i}"),
191 });
192 }
193
194 for (i, item) in target.iter().enumerate().take(tgt_len).skip(src_len) {
196 ops.push(PatchOperation::Add {
197 path: format!("{path}/{i}"),
198 value: item.clone(),
199 });
200 }
201}
202
203fn diff_arrays_optimized(
208 source: &[Value],
209 target: &[Value],
210 path: &str,
211 ops: &mut JsonPatch,
212 options: &DiffOptions,
213) {
214 if source.len() <= 4 || target.len() <= 4 {
216 diff_arrays_simple(source, target, path, ops, options);
217 return;
218 }
219
220 let lcs = compute_lcs(source, target);
222
223 generate_patch_from_lcs(source, target, &lcs, path, ops, options);
225}
226
227fn compute_lcs(source: &[Value], target: &[Value]) -> Vec<(usize, usize)> {
229 let m = source.len();
230 let n = target.len();
231
232 let mut dp = vec![vec![0usize; n + 1]; m + 1];
234
235 for (i, src_val) in source.iter().enumerate() {
236 for (j, tgt_val) in target.iter().enumerate() {
237 if values_equal_fast(src_val, tgt_val) {
238 dp[i + 1][j + 1] = dp[i][j] + 1;
239 } else {
240 dp[i + 1][j + 1] = dp[i + 1][j].max(dp[i][j + 1]);
241 }
242 }
243 }
244
245 let mut lcs = Vec::new();
247 let mut i = m;
248 let mut j = n;
249
250 while i > 0 && j > 0 {
251 if values_equal_fast(&source[i - 1], &target[j - 1]) {
252 lcs.push((i - 1, j - 1));
253 i -= 1;
254 j -= 1;
255 } else if dp[i - 1][j] > dp[i][j - 1] {
256 i -= 1;
257 } else {
258 j -= 1;
259 }
260 }
261
262 lcs.reverse();
263 lcs
264}
265
266fn generate_patch_from_lcs(
268 source: &[Value],
269 target: &[Value],
270 lcs: &[(usize, usize)],
271 path: &str,
272 ops: &mut JsonPatch,
273 options: &DiffOptions,
274) {
275 let mut src_idx = 0;
276 let mut tgt_idx = 0;
277 let mut lcs_idx = 0;
278
279 let mut removals: Vec<usize> = Vec::new();
281
282 while tgt_idx < target.len() {
283 if lcs_idx < lcs.len() && lcs[lcs_idx].1 == tgt_idx {
284 let (src_lcs, _) = lcs[lcs_idx];
286
287 while src_idx < src_lcs {
289 removals.push(src_idx);
290 src_idx += 1;
291 }
292
293 let elem_path = format!("{path}/{tgt_idx}");
295 diff_values(&source[src_lcs], &target[tgt_idx], &elem_path, ops, options);
296
297 src_idx += 1;
298 tgt_idx += 1;
299 lcs_idx += 1;
300 } else {
301 ops.push(PatchOperation::Add {
303 path: format!("{path}/{tgt_idx}"),
304 value: target[tgt_idx].clone(),
305 });
306 tgt_idx += 1;
307 }
308 }
309
310 while src_idx < source.len() {
312 removals.push(src_idx);
313 src_idx += 1;
314 }
315
316 for &idx in removals.iter().rev() {
318 ops.push(PatchOperation::Remove {
319 path: format!("{path}/{idx}"),
320 });
321 }
322}
323
324fn format_path(base: &str, key: &str) -> String {
326 let escaped = key.replace('~', "~0").replace('/', "~1");
328 format!("{base}/{escaped}")
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use serde_json::json;
335
336 #[test]
337 fn test_diff_identical() {
338 let a = json!({"foo": "bar", "baz": 42});
339 let patch = json_diff(&a, &a);
340 assert!(patch.is_empty());
341 }
342
343 #[test]
344 fn test_diff_add_field() {
345 let a = json!({"foo": "bar"});
346 let b = json!({"foo": "bar", "baz": 42});
347 let patch = json_diff(&a, &b);
348
349 assert_eq!(patch.len(), 1);
350 assert!(
351 matches!(&patch.operations[0], PatchOperation::Add { path, value }
352 if path == "/baz" && *value == json!(42))
353 );
354 }
355
356 #[test]
357 fn test_diff_remove_field() {
358 let a = json!({"foo": "bar", "baz": 42});
359 let b = json!({"foo": "bar"});
360 let patch = json_diff(&a, &b);
361
362 assert_eq!(patch.len(), 1);
363 assert!(
364 matches!(&patch.operations[0], PatchOperation::Remove { path }
365 if path == "/baz")
366 );
367 }
368
369 #[test]
370 fn test_diff_replace_value() {
371 let a = json!({"foo": "bar"});
372 let b = json!({"foo": "baz"});
373 let patch = json_diff(&a, &b);
374
375 assert_eq!(patch.len(), 1);
376 assert!(
377 matches!(&patch.operations[0], PatchOperation::Replace { path, value }
378 if path == "/foo" && *value == json!("baz"))
379 );
380 }
381
382 #[test]
383 fn test_diff_nested_change() {
384 let a = json!({"outer": {"inner": "old"}});
385 let b = json!({"outer": {"inner": "new"}});
386 let patch = json_diff(&a, &b);
387
388 assert_eq!(patch.len(), 1);
389 assert!(
390 matches!(&patch.operations[0], PatchOperation::Replace { path, .. }
391 if path == "/outer/inner")
392 );
393 }
394
395 #[test]
396 fn test_diff_array_append() {
397 let a = json!({"arr": [1, 2]});
398 let b = json!({"arr": [1, 2, 3]});
399 let patch = json_diff(&a, &b);
400
401 assert_eq!(patch.len(), 1);
402 assert!(
403 matches!(&patch.operations[0], PatchOperation::Add { path, value }
404 if path == "/arr/2" && *value == json!(3))
405 );
406 }
407
408 #[test]
409 fn test_diff_array_remove() {
410 let a = json!({"arr": [1, 2, 3]});
411 let b = json!({"arr": [1, 2]});
412 let patch = json_diff(&a, &b);
413
414 assert_eq!(patch.len(), 1);
415 assert!(
416 matches!(&patch.operations[0], PatchOperation::Remove { path }
417 if path == "/arr/2")
418 );
419 }
420
421 #[test]
422 fn test_diff_type_change() {
423 let a = json!({"foo": "bar"});
424 let b = json!({"foo": 42});
425 let patch = json_diff(&a, &b);
426
427 assert_eq!(patch.len(), 1);
428 assert!(
429 matches!(&patch.operations[0], PatchOperation::Replace { path, value }
430 if path == "/foo" && *value == json!(42))
431 );
432 }
433
434 #[test]
435 fn test_diff_root_replace() {
436 let a = json!("foo");
437 let b = json!(42);
438 let patch = json_diff(&a, &b);
439
440 assert_eq!(patch.len(), 1);
441 assert!(
442 matches!(&patch.operations[0], PatchOperation::Replace { path, .. }
443 if path.is_empty())
444 );
445 }
446
447 #[test]
448 fn test_diff_roundtrip() {
449 use super::super::patch::apply_patch;
450
451 let a = json!({
452 "name": "Alice",
453 "age": 30,
454 "hobbies": ["reading", "swimming"],
455 "address": {
456 "city": "New York",
457 "zip": "10001"
458 }
459 });
460
461 let b = json!({
462 "name": "Alice",
463 "age": 31,
464 "hobbies": ["reading", "cycling"],
465 "address": {
466 "city": "Boston",
467 "zip": "02101"
468 },
469 "email": "alice@example.com"
470 });
471
472 let patch = json_diff(&a, &b);
473 let result = apply_patch(&a, &patch).unwrap();
474 assert_eq!(result, b);
475 }
476
477 #[test]
478 fn test_diff_with_special_chars() {
479 let a = json!({"foo/bar": "old"});
480 let b = json!({"foo/bar": "new"});
481 let patch = json_diff(&a, &b);
482
483 assert_eq!(patch.len(), 1);
484 assert!(
486 matches!(&patch.operations[0], PatchOperation::Replace { path, .. }
487 if path == "/foo~1bar")
488 );
489 }
490
491 #[test]
492 fn test_diff_empty_to_populated() {
493 let a = json!({});
494 let b = json!({"foo": "bar", "baz": [1, 2, 3]});
495 let patch = json_diff(&a, &b);
496
497 assert_eq!(patch.len(), 2);
499 }
500
501 #[test]
502 fn test_diff_options_default() {
503 let options = DiffOptions::default();
504 assert!(!options.detect_moves);
505 assert!(!options.detect_copies);
506 assert!(!options.optimize_arrays);
507 }
508
509 #[test]
510 fn test_diff_array_optimized() {
511 use super::super::patch::apply_patch;
512 let a = json!([1, 2, 3, 4, 5]);
513 let b = json!([1, 3, 5, 6]);
514
515 let options = DiffOptions::default().with_array_optimization();
516 let patch = json_diff_with_options(&a, &b, &options);
517
518 let result = apply_patch(&a, &patch).unwrap();
520 assert_eq!(result, b);
521 }
522
523 #[test]
524 fn test_values_equal_fast_strings() {
525 assert!(values_equal_fast(
526 &json!("hello world"),
527 &json!("hello world")
528 ));
529 assert!(!values_equal_fast(&json!("hello"), &json!("world")));
530 }
531
532 #[test]
533 fn test_values_equal_fast_numbers() {
534 assert!(values_equal_fast(&json!(42), &json!(42)));
535 assert!(!values_equal_fast(&json!(42), &json!(43)));
536 assert!(values_equal_fast(&json!(1.5), &json!(1.5)));
537 }
538
539 #[test]
540 fn test_values_equal_fast_nested() {
541 let a = json!({"arr": [1, 2, {"nested": true}]});
542 let b = json!({"arr": [1, 2, {"nested": true}]});
543 let c = json!({"arr": [1, 2, {"nested": false}]});
544
545 assert!(values_equal_fast(&a, &b));
546 assert!(!values_equal_fast(&a, &c));
547 }
548
549 #[test]
554 fn test_diff_options_with_moves() {
555 let opts = DiffOptions::default().with_moves();
556 assert!(opts.detect_moves);
557 assert!(!opts.detect_copies);
558 assert!(!opts.optimize_arrays);
559 }
560
561 #[test]
562 fn test_diff_options_with_copies() {
563 let opts = DiffOptions::default().with_copies();
564 assert!(!opts.detect_moves);
565 assert!(opts.detect_copies);
566 assert!(!opts.optimize_arrays);
567 }
568
569 #[test]
570 fn test_diff_options_with_array_optimization() {
571 let opts = DiffOptions::default().with_array_optimization();
572 assert!(!opts.detect_moves);
573 assert!(!opts.detect_copies);
574 assert!(opts.optimize_arrays);
575 }
576
577 #[test]
578 fn test_diff_options_chained() {
579 let opts = DiffOptions::default()
580 .with_moves()
581 .with_copies()
582 .with_array_optimization();
583 assert!(opts.detect_moves);
584 assert!(opts.detect_copies);
585 assert!(opts.optimize_arrays);
586 }
587
588 #[test]
589 fn test_diff_options_debug() {
590 let opts = DiffOptions::default().with_moves();
591 let debug_str = format!("{opts:?}");
592 assert!(debug_str.contains("DiffOptions"));
593 assert!(debug_str.contains("detect_moves"));
594 }
595
596 #[test]
597 fn test_diff_options_clone() {
598 let opts = DiffOptions::default()
599 .with_copies()
600 .with_array_optimization();
601 #[allow(clippy::redundant_clone)] let cloned = opts.clone();
603 assert_eq!(cloned.detect_copies, opts.detect_copies);
604 assert_eq!(cloned.optimize_arrays, opts.optimize_arrays);
605 }
606
607 #[test]
612 fn test_values_equal_fast_null() {
613 assert!(values_equal_fast(&json!(null), &json!(null)));
614 assert!(!values_equal_fast(&json!(null), &json!(0)));
615 }
616
617 #[test]
618 fn test_values_equal_fast_bool() {
619 assert!(values_equal_fast(&json!(true), &json!(true)));
620 assert!(values_equal_fast(&json!(false), &json!(false)));
621 assert!(!values_equal_fast(&json!(true), &json!(false)));
622 assert!(!values_equal_fast(&json!(false), &json!(true)));
623 }
624
625 #[test]
626 fn test_values_equal_fast_different_types() {
627 assert!(!values_equal_fast(&json!(null), &json!(false)));
628 assert!(!values_equal_fast(&json!(0), &json!("0")));
629 assert!(!values_equal_fast(&json!([]), &json!({})));
630 assert!(!values_equal_fast(&json!(""), &json!(null)));
631 assert!(!values_equal_fast(&json!(1), &json!(true)));
632 }
633
634 #[test]
635 fn test_values_equal_fast_array_different_lengths() {
636 assert!(!values_equal_fast(&json!([1, 2]), &json!([1, 2, 3])));
637 assert!(!values_equal_fast(&json!([1, 2, 3]), &json!([1, 2])));
638 }
639
640 #[test]
641 fn test_values_equal_fast_array_empty() {
642 assert!(values_equal_fast(&json!([]), &json!([])));
643 }
644
645 #[test]
646 fn test_values_equal_fast_object_different_lengths() {
647 assert!(!values_equal_fast(
648 &json!({"a": 1}),
649 &json!({"a": 1, "b": 2})
650 ));
651 assert!(!values_equal_fast(
652 &json!({"a": 1, "b": 2}),
653 &json!({"a": 1})
654 ));
655 }
656
657 #[test]
658 fn test_values_equal_fast_object_missing_key() {
659 assert!(!values_equal_fast(&json!({"a": 1}), &json!({"b": 1})));
661 }
662
663 #[test]
664 fn test_values_equal_fast_object_empty() {
665 assert!(values_equal_fast(&json!({}), &json!({})));
666 }
667
668 #[test]
669 fn test_values_equal_fast_long_strings() {
670 let long1 = "a".repeat(1000);
671 let long2 = "a".repeat(1000);
672 let long3 = "a".repeat(999) + "b";
673 assert!(values_equal_fast(&json!(long1), &json!(long2)));
674 assert!(!values_equal_fast(&json!(long1), &json!(long3)));
675 }
676
677 #[test]
682 fn test_format_path_simple() {
683 assert_eq!(format_path("", "foo"), "/foo");
684 assert_eq!(format_path("/root", "child"), "/root/child");
685 }
686
687 #[test]
688 fn test_format_path_with_slash() {
689 assert_eq!(format_path("", "foo/bar"), "/foo~1bar");
690 }
691
692 #[test]
693 fn test_format_path_with_tilde() {
694 assert_eq!(format_path("", "foo~bar"), "/foo~0bar");
695 }
696
697 #[test]
698 fn test_format_path_with_both_special_chars() {
699 assert_eq!(format_path("", "~a/b~"), "/~0a~1b~0");
700 }
701
702 #[test]
707 fn test_compute_lcs_identical() {
708 let arr = vec![json!(1), json!(2), json!(3)];
709 let lcs = compute_lcs(&arr, &arr);
710 assert_eq!(lcs.len(), 3);
711 assert_eq!(lcs, vec![(0, 0), (1, 1), (2, 2)]);
712 }
713
714 #[test]
715 fn test_compute_lcs_empty_source() {
716 let lcs = compute_lcs(&[], &[json!(1), json!(2)]);
717 assert!(lcs.is_empty());
718 }
719
720 #[test]
721 fn test_compute_lcs_empty_target() {
722 let lcs = compute_lcs(&[json!(1), json!(2)], &[]);
723 assert!(lcs.is_empty());
724 }
725
726 #[test]
727 fn test_compute_lcs_no_common() {
728 let source = vec![json!(1), json!(2)];
729 let target = vec![json!(3), json!(4)];
730 let lcs = compute_lcs(&source, &target);
731 assert!(lcs.is_empty());
732 }
733
734 #[test]
735 fn test_compute_lcs_partial_overlap() {
736 let source = vec![json!(1), json!(2), json!(3), json!(4)];
737 let target = vec![json!(1), json!(3), json!(5)];
738 let lcs = compute_lcs(&source, &target);
739 assert_eq!(lcs.len(), 2);
741 }
742
743 #[test]
744 fn test_compute_lcs_interleaved() {
745 let source = vec![json!(1), json!(3), json!(5), json!(7)];
746 let target = vec![json!(2), json!(3), json!(4), json!(5), json!(6)];
747 let lcs = compute_lcs(&source, &target);
748 assert_eq!(lcs.len(), 2);
750 }
751
752 #[test]
757 fn test_diff_arrays_simple_all_different() {
758 let a = json!([1, 2, 3]);
759 let b = json!([4, 5, 6]);
760 let patch = json_diff(&a, &b);
761
762 assert_eq!(patch.len(), 3);
764 for op in &patch.operations {
765 assert!(matches!(op, PatchOperation::Replace { .. }));
766 }
767 }
768
769 #[test]
770 fn test_diff_arrays_remove_multiple() {
771 let a = json!([1, 2, 3, 4, 5]);
772 let b = json!([1, 2]);
773 let patch = json_diff(&a, &b);
774
775 let removes_count = patch
777 .operations
778 .iter()
779 .filter(|op| matches!(op, PatchOperation::Remove { .. }))
780 .count();
781 assert_eq!(removes_count, 3);
782 }
783
784 #[test]
785 fn test_diff_arrays_add_multiple() {
786 let a = json!([1]);
787 let b = json!([1, 2, 3, 4]);
788 let patch = json_diff(&a, &b);
789
790 let adds_count = patch
792 .operations
793 .iter()
794 .filter(|op| matches!(op, PatchOperation::Add { .. }))
795 .count();
796 assert_eq!(adds_count, 3);
797 }
798
799 #[test]
800 fn test_diff_arrays_optimized_large() {
801 let a = json!([1, 2, 3, 4, 5, 6, 7, 8]);
803 let b = json!([1, 3, 5, 7, 9]);
804
805 let options = DiffOptions::default().with_array_optimization();
806 let patch = json_diff_with_options(&a, &b, &options);
807
808 assert!(!patch.is_empty());
812 }
813
814 #[test]
815 fn test_diff_arrays_optimized_insertion_in_middle() {
816 let a = json!([1, 2, 3, 4, 5, 6]);
817 let b = json!([1, 2, 100, 3, 4, 5, 6]);
818
819 let options = DiffOptions::default().with_array_optimization();
820 let patch = json_diff_with_options(&a, &b, &options);
821
822 assert!(!patch.is_empty());
824 }
825
826 #[test]
827 fn test_diff_arrays_optimized_deletion_from_middle() {
828 let a = json!([1, 2, 3, 4, 5, 6, 7, 8]);
829 let b = json!([1, 2, 5, 6, 7, 8]);
830
831 let options = DiffOptions::default().with_array_optimization();
832 let patch = json_diff_with_options(&a, &b, &options);
833
834 assert!(!patch.is_empty());
836 }
837
838 #[test]
839 fn test_diff_arrays_optimized_complete_replacement() {
840 let a = json!([1, 2, 3, 4, 5, 6]);
841 let b = json!([10, 20, 30, 40, 50, 60]);
842
843 let options = DiffOptions::default().with_array_optimization();
844 let patch = json_diff_with_options(&a, &b, &options);
845
846 assert!(!patch.is_empty());
848 }
849
850 #[test]
851 fn test_diff_arrays_small_uses_simple() {
852 use super::super::patch::apply_patch;
853 let a = json!([1, 2, 3, 4]);
855 let b = json!([1, 3, 5]);
856
857 let options = DiffOptions::default().with_array_optimization();
858 let patch = json_diff_with_options(&a, &b, &options);
859
860 let result = apply_patch(&a, &patch).unwrap();
861 assert_eq!(result, b);
862 }
863
864 #[test]
869 fn test_diff_objects_multiple_changes() {
870 let a = json!({
871 "keep": "same",
872 "modify": "old",
873 "remove": "gone"
874 });
875 let b = json!({
876 "keep": "same",
877 "modify": "new",
878 "add": "fresh"
879 });
880 let patch = json_diff(&a, &b);
881
882 assert_eq!(patch.len(), 3);
884 }
885
886 #[test]
887 fn test_diff_objects_nested_add() {
888 let a = json!({"outer": {}});
889 let b = json!({"outer": {"inner": "value"}});
890 let patch = json_diff(&a, &b);
891
892 assert_eq!(patch.len(), 1);
893 assert!(
894 matches!(&patch.operations[0], PatchOperation::Add { path, .. }
895 if path == "/outer/inner")
896 );
897 }
898
899 #[test]
900 fn test_diff_objects_nested_remove() {
901 let a = json!({"outer": {"inner": "value"}});
902 let b = json!({"outer": {}});
903 let patch = json_diff(&a, &b);
904
905 assert_eq!(patch.len(), 1);
906 assert!(
907 matches!(&patch.operations[0], PatchOperation::Remove { path }
908 if path == "/outer/inner")
909 );
910 }
911
912 #[test]
913 fn test_diff_deep_nesting() {
914 let a = json!({"a": {"b": {"c": {"d": "old"}}}});
915 let b = json!({"a": {"b": {"c": {"d": "new"}}}});
916 let patch = json_diff(&a, &b);
917
918 assert_eq!(patch.len(), 1);
919 assert!(
920 matches!(&patch.operations[0], PatchOperation::Replace { path, .. }
921 if path == "/a/b/c/d")
922 );
923 }
924
925 #[test]
930 fn test_diff_empty_array() {
931 let a = json!([]);
932 let b = json!([]);
933 let patch = json_diff(&a, &b);
934 assert!(patch.is_empty());
935 }
936
937 #[test]
938 fn test_diff_empty_to_array() {
939 let a = json!([]);
940 let b = json!([1, 2, 3]);
941 let patch = json_diff(&a, &b);
942 assert_eq!(patch.len(), 3);
943 }
944
945 #[test]
946 fn test_diff_array_to_empty() {
947 let a = json!([1, 2, 3]);
948 let b = json!([]);
949 let patch = json_diff(&a, &b);
950 assert_eq!(patch.len(), 3);
951 }
952
953 #[test]
954 fn test_diff_null_values() {
955 let a = json!({"key": null});
956 let b = json!({"key": "value"});
957 let patch = json_diff(&a, &b);
958
959 assert_eq!(patch.len(), 1);
960 assert!(matches!(
961 &patch.operations[0],
962 PatchOperation::Replace { .. }
963 ));
964 }
965
966 #[test]
967 fn test_diff_boolean_values() {
968 let a = json!({"flag": true});
969 let b = json!({"flag": false});
970 let patch = json_diff(&a, &b);
971
972 assert_eq!(patch.len(), 1);
973 }
974
975 #[test]
976 fn test_diff_number_precision() {
977 let a = json!({"value": 0.1});
978 let b = json!({"value": 0.1});
979 let patch = json_diff(&a, &b);
980 assert!(patch.is_empty());
981 }
982
983 #[test]
984 fn test_diff_array_object_mix() {
985 let a = json!([{"a": 1}, {"b": 2}]);
986 let b = json!([{"a": 1}, {"b": 3}]);
987 let patch = json_diff(&a, &b);
988
989 assert_eq!(patch.len(), 1);
990 assert!(
991 matches!(&patch.operations[0], PatchOperation::Replace { path, .. }
992 if path == "/1/b")
993 );
994 }
995
996 #[test]
997 fn test_diff_with_options_no_optimization() {
998 use super::super::patch::apply_patch;
999 let a = json!([1, 2, 3, 4, 5, 6, 7, 8]);
1000 let b = json!([1, 3, 5, 7]);
1001
1002 let opts = DiffOptions::default();
1004 let patch = json_diff_with_options(&a, &b, &opts);
1005
1006 let result = apply_patch(&a, &patch).unwrap();
1007 assert_eq!(result, b);
1008 }
1009
1010 #[test]
1011 fn test_generate_patch_from_lcs_edge_cases() {
1012 let source = vec![json!(1), json!(2), json!(3), json!(4), json!(5)];
1014 let target = vec![json!(1), json!(2)];
1015
1016 let lcs = compute_lcs(&source, &target);
1017 let mut patch = JsonPatch::new();
1018 let opts = DiffOptions::default();
1019 generate_patch_from_lcs(&source, &target, &lcs, "", &mut patch, &opts);
1020
1021 let removes_count = patch
1023 .operations
1024 .iter()
1025 .filter(|op| matches!(op, PatchOperation::Remove { .. }))
1026 .count();
1027 assert_eq!(removes_count, 3);
1028 }
1029
1030 #[test]
1031 fn test_diff_complex_roundtrip() {
1032 use super::super::patch::apply_patch;
1033 let a = json!({
1034 "users": [
1035 {"id": 1, "name": "Alice", "tags": ["admin", "active"]},
1036 {"id": 2, "name": "Bob", "tags": ["user"]},
1037 {"id": 3, "name": "Charlie", "tags": ["user", "inactive"]}
1038 ],
1039 "metadata": {
1040 "version": "1.0",
1041 "count": 3
1042 }
1043 });
1044
1045 let b = json!({
1046 "users": [
1047 {"id": 1, "name": "Alice", "tags": ["admin", "active", "vip"]},
1048 {"id": 3, "name": "Charlie Updated", "tags": ["user"]}
1049 ],
1050 "metadata": {
1051 "version": "2.0",
1052 "count": 2,
1053 "updated": true
1054 }
1055 });
1056
1057 let patch = json_diff(&a, &b);
1058 let result = apply_patch(&a, &patch).unwrap();
1059 assert_eq!(result, b);
1060 }
1061}