Skip to main content

diff_json/
diff.rs

1use serde_json::Value;
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum DiffType {
6    Added,
7    Removed,
8    Modified,
9    Moved,
10}
11
12#[derive(Debug, Clone)]
13pub struct Diff {
14    pub path: String,
15    pub diff_type: DiffType,
16    pub old_value: Option<Value>,
17    pub new_value: Option<Value>,
18}
19
20impl fmt::Display for Diff {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match &self.diff_type {
23            DiffType::Added => {
24                write!(f, "Added at '{}': {:?}", self.path, self.new_value)
25            }
26            DiffType::Removed => {
27                write!(f, "Removed from '{}': {:?}", self.path, self.old_value)
28            }
29            DiffType::Modified => {
30                write!(
31                    f,
32                    "Modified at '{}': {:?} -> {:?}",
33                    self.path, self.old_value, self.new_value
34                )
35            }
36            DiffType::Moved => {
37                write!(
38                    f,
39                    "Moved: {} -> {}",
40                    self.path,
41                    self.new_value.as_ref().unwrap()
42                )
43            }
44        }
45    }
46}
47
48pub struct JsonDiff {
49    ignore_order: bool,
50}
51
52impl JsonDiff {
53    pub fn new() -> Self {
54        Self {
55            ignore_order: false,
56        }
57    }
58
59    pub fn ignore_order(mut self, ignore: bool) -> Self {
60        self.ignore_order = ignore;
61        self
62    }
63
64    pub fn diff(&self, v1: &Value, v2: &Value) -> Vec<Diff> {
65        let mut diffs = Vec::new();
66        self.diff_values(v1, v2, "", &mut diffs);
67        diffs
68    }
69
70    fn diff_values(&self, v1: &Value, v2: &Value, path: &str, diffs: &mut Vec<Diff>) {
71        match (v1, v2) {
72            (Value::Null, Value::Null) => {}
73            (Value::Bool(b1), Value::Bool(b2)) if b1 == b2 => {}
74            (Value::Number(n1), Value::Number(n2)) if n1 == n2 => {}
75            (Value::String(s1), Value::String(s2)) if s1 == s2 => {}
76            (Value::Array(a1), Value::Array(a2)) => {
77                self.diff_arrays(a1, a2, path, diffs);
78            }
79            (Value::Object(o1), Value::Object(o2)) => {
80                self.diff_objects(o1, o2, path, diffs);
81            }
82            _ => {
83                diffs.push(Diff {
84                    path: path.to_string(),
85                    diff_type: DiffType::Modified,
86                    old_value: Some(v1.clone()),
87                    new_value: Some(v2.clone()),
88                });
89            }
90        }
91    }
92
93    fn diff_arrays(&self, a1: &[Value], a2: &[Value], path: &str, diffs: &mut Vec<Diff>) {
94        if self.ignore_order {
95            self.diff_arrays_ignore_order(a1, a2, path, diffs);
96        } else {
97            self.diff_arrays_preserve_order(a1, a2, path, diffs);
98        }
99    }
100
101    fn diff_arrays_preserve_order(
102        &self,
103        a1: &[Value],
104        a2: &[Value],
105        path: &str,
106        diffs: &mut Vec<Diff>,
107    ) {
108        let max_len = a1.len().max(a2.len());
109
110        for i in 0..max_len {
111            let new_path = if path.is_empty() {
112                format!("[{}]", i)
113            } else {
114                format!("{}[{}]", path, i)
115            };
116
117            match (a1.get(i), a2.get(i)) {
118                (Some(v1), Some(v2)) => {
119                    self.diff_values(v1, v2, &new_path, diffs);
120                }
121                (Some(v1), None) => {
122                    diffs.push(Diff {
123                        path: new_path,
124                        diff_type: DiffType::Removed,
125                        old_value: Some(v1.clone()),
126                        new_value: None,
127                    });
128                }
129                (None, Some(v2)) => {
130                    diffs.push(Diff {
131                        path: new_path,
132                        diff_type: DiffType::Added,
133                        old_value: None,
134                        new_value: Some(v2.clone()),
135                    });
136                }
137                (None, None) => {}
138            }
139        }
140    }
141
142    fn diff_arrays_ignore_order(
143        &self,
144        a1: &[Value],
145        a2: &[Value],
146        path: &str,
147        diffs: &mut Vec<Diff>,
148    ) {
149        let mut unused1: Vec<bool> = vec![false; a1.len()];
150        let mut unused2: Vec<bool> = vec![false; a2.len()];
151
152        for (i, v1) in a1.iter().enumerate() {
153            let mut found = false;
154            for (j, v2) in a2.iter().enumerate() {
155                if !unused2[j] && self.values_equal(v1, v2) {
156                    unused1[i] = true;
157                    unused2[j] = true;
158                    found = true;
159                    break;
160                }
161            }
162
163            if !found {
164                let new_path = if path.is_empty() {
165                    format!("[{}]", i)
166                } else {
167                    format!("{}[{}]", path, i)
168                };
169
170                diffs.push(Diff {
171                    path: new_path,
172                    diff_type: DiffType::Removed,
173                    old_value: Some(v1.clone()),
174                    new_value: None,
175                });
176            }
177        }
178
179        for (j, v2) in a2.iter().enumerate() {
180            if !unused2[j] {
181                let new_path = if path.is_empty() {
182                    format!("[{}]", j)
183                } else {
184                    format!("{}[{}]", path, j)
185                };
186
187                diffs.push(Diff {
188                    path: new_path,
189                    diff_type: DiffType::Added,
190                    old_value: None,
191                    new_value: Some(v2.clone()),
192                });
193            }
194        }
195    }
196
197    fn diff_objects(
198        &self,
199        o1: &serde_json::Map<String, Value>,
200        o2: &serde_json::Map<String, Value>,
201        path: &str,
202        diffs: &mut Vec<Diff>,
203    ) {
204        let all_keys: std::collections::HashSet<&String> = o1.keys().chain(o2.keys()).collect();
205
206        for key in all_keys {
207            let new_path = if path.is_empty() {
208                key.clone()
209            } else {
210                format!("{}.{}", path, key)
211            };
212
213            match (o1.get(key), o2.get(key)) {
214                (Some(v1), Some(v2)) => {
215                    self.diff_values(v1, v2, &new_path, diffs);
216                }
217                (Some(v1), None) => {
218                    diffs.push(Diff {
219                        path: new_path,
220                        diff_type: DiffType::Removed,
221                        old_value: Some(v1.clone()),
222                        new_value: None,
223                    });
224                }
225                (None, Some(v2)) => {
226                    diffs.push(Diff {
227                        path: new_path,
228                        diff_type: DiffType::Added,
229                        old_value: None,
230                        new_value: Some(v2.clone()),
231                    });
232                }
233                (None, None) => {}
234            }
235        }
236    }
237
238    fn values_equal(&self, v1: &Value, v2: &Value) -> bool {
239        match (v1, v2) {
240            (Value::Null, Value::Null) => true,
241            (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
242            (Value::Number(n1), Value::Number(n2)) => n1 == n2,
243            (Value::String(s1), Value::String(s2)) => s1 == s2,
244            (Value::Array(a1), Value::Array(a2)) => {
245                if a1.len() != a2.len() {
246                    return false;
247                }
248                if self.ignore_order {
249                    self.arrays_equal_ignore_order(a1, a2)
250                } else {
251                    a1.iter()
252                        .zip(a2.iter())
253                        .all(|(v1, v2)| self.values_equal(v1, v2))
254                }
255            }
256            (Value::Object(o1), Value::Object(o2)) => {
257                if o1.len() != o2.len() {
258                    return false;
259                }
260                o1.keys().all(|k| match (o1.get(k), o2.get(k)) {
261                    (Some(v1), Some(v2)) => self.values_equal(v1, v2),
262                    _ => false,
263                })
264            }
265            _ => false,
266        }
267    }
268
269    fn arrays_equal_ignore_order(&self, a1: &[Value], a2: &[Value]) -> bool {
270        if a1.len() != a2.len() {
271            return false;
272        }
273
274        let mut used = vec![false; a2.len()];
275
276        for v1 in a1 {
277            let mut found = false;
278            for (j, v2) in a2.iter().enumerate() {
279                if !used[j] && self.values_equal(v1, v2) {
280                    used[j] = true;
281                    found = true;
282                    break;
283                }
284            }
285            if !found {
286                return false;
287            }
288        }
289
290        true
291    }
292}
293
294impl Default for JsonDiff {
295    fn default() -> Self {
296        Self::new()
297    }
298}