json_traits/diff.rs
1use std::cmp::Ordering;
2use std::collections::BTreeMap;
3
4use serde_json::Value;
5
6use crate::{JsonPaths, JsonScalar};
7
8/// Compares JSON values, or maps of paths, by leaf value.
9///
10/// This is the Rust form of `DiffWith`. The pair is `(self, other)`.
11/// [`JsonScalar::Undefined`] fills the side where a path is absent.
12pub trait DiffJson {
13 /// Paths whose leaf values differ.
14 ///
15 /// Paths that are equal on both sides are omitted. JSON `null` equals
16 /// JSON `null`.
17 ///
18 /// # Examples
19 ///
20 /// ```
21 /// use json_traits::{DiffJson, JsonScalar};
22 /// use serde_json::json;
23 ///
24 /// let left = json!({ "name": "Stewie", "number": 12 });
25 /// let right = json!({ "name": "Stewie", "number": 13, "isAwesome": true });
26 ///
27 /// let diff = left.diff_with(&right);
28 /// assert_eq!(
29 /// diff.get("number"),
30 /// Some(&(JsonScalar::from_integer(12), JsonScalar::from_integer(13)))
31 /// );
32 /// assert_eq!(
33 /// diff.get("isAwesome"),
34 /// Some(&(JsonScalar::Undefined, JsonScalar::from(true)))
35 /// );
36 /// assert!(!diff.contains_key("name"));
37 /// ```
38 #[doc(alias = "DiffWith")]
39 #[must_use]
40 fn diff_with(&self, other: &Self) -> BTreeMap<String, (JsonScalar, JsonScalar)>;
41}
42
43impl DiffJson for Value {
44 fn diff_with(&self, other: &Self) -> BTreeMap<String, (JsonScalar, JsonScalar)> {
45 self.paths_and_values().diff_with(&other.paths_and_values())
46 }
47}
48
49impl DiffJson for BTreeMap<String, JsonScalar> {
50 fn diff_with(&self, other: &Self) -> BTreeMap<String, (JsonScalar, JsonScalar)> {
51 // Both maps are already ordered, so walk them together. Equal leaves
52 // are skipped without cloning their paths.
53 let mut diff = BTreeMap::new();
54 let mut left_keys = self.iter();
55 let mut right_keys = other.iter();
56 let mut left = left_keys.next();
57 let mut right = right_keys.next();
58 loop {
59 match (left, right) {
60 (None, None) => break,
61 (Some((key, value)), None) => {
62 diff.insert(key.clone(), (value.clone(), JsonScalar::Undefined));
63 left = left_keys.next();
64 }
65 (None, Some((key, value))) => {
66 diff.insert(key.clone(), (JsonScalar::Undefined, value.clone()));
67 right = right_keys.next();
68 }
69 (Some((left_key, left_value)), Some((right_key, right_value))) => {
70 match left_key.cmp(right_key) {
71 Ordering::Less => {
72 diff.insert(
73 left_key.clone(),
74 (left_value.clone(), JsonScalar::Undefined),
75 );
76 left = left_keys.next();
77 }
78 Ordering::Greater => {
79 diff.insert(
80 right_key.clone(),
81 (JsonScalar::Undefined, right_value.clone()),
82 );
83 right = right_keys.next();
84 }
85 Ordering::Equal => {
86 if left_value != right_value {
87 diff.insert(
88 left_key.clone(),
89 (left_value.clone(), right_value.clone()),
90 );
91 }
92 left = left_keys.next();
93 right = right_keys.next();
94 }
95 }
96 }
97 }
98 }
99 diff
100 }
101}