1use serde_json::Value;
16
17#[derive(Debug, PartialEq, Eq, Clone)]
18pub struct Difference {
19 pub path: String,
21 pub before: Option<Value>,
23 pub after: Option<Value>,
25}
26
27fn same_json_type(a: &Value, b: &Value) -> bool {
29 std::mem::discriminant(a) == std::mem::discriminant(b)
30}
31
32fn recurse(a: &Value, b: &Value, differences: &mut Vec<Difference>, path: String) {
33 if !same_json_type(a, b) {
34 differences.push(Difference {
35 path: path.clone(),
36 before: Some(a.clone()),
37 after: Some(b.clone()),
38 });
39 return;
40 }
41 match a {
42 Value::String(_) | Value::Number(_) | Value::Bool(_) | Value::Null => {
44 if a != b {
45 differences.push(Difference {
46 path: path.clone(),
47 before: Some(a.clone()),
48 after: Some(b.clone()),
49 })
50 }
51 }
52 Value::Array(a_values) => {
54 let b_values = b.as_array().unwrap();
55 for i in 0..a_values.len().max(b_values.len()) {
56 let va = a_values.get(i).unwrap_or(&Value::Null);
57 let vb = b_values.get(i).unwrap_or(&Value::Null);
58 recurse(va, vb, differences, format!("{}[{}]", path, i));
59 }
60 }
61 Value::Object(map) => {
63 for (ak, av) in map {
64 match b.get(ak) {
65 Some(bv) => {
66 let full_path = if path.is_empty() {
67 ak.to_string()
68 } else {
69 format!("{}.{}", path, ak)
70 };
71 recurse(av, bv, differences, full_path);
72 }
73 None => differences.push(Difference {
74 path: format!("{}", ak),
75 before: Some(av.clone()),
76 after: None,
77 }),
78 }
79 }
80 for (bk, bv) in b.as_object().unwrap() {
81 if !map.contains_key(bk) {
82 let full_path = if path.is_empty() {
83 bk.to_string()
84 } else {
85 format!("{}.{}", path, bk)
86 };
87 differences.push(Difference {
88 path: full_path,
89 before: None,
90 after: Some(bv.clone()),
91 });
92 }
93 }
94 }
95 }
96}
97
98pub fn deep_diff(a: &Value, b: &Value) -> Vec<Difference> {
100 let mut differences = Vec::new();
101 recurse(a, b, &mut differences, "".to_string());
102 differences
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use serde_json::{Value, json};
109
110 #[test]
112 fn test_no_change() {
113 let a = json!("Alice");
114 let result = deep_diff(&a, &a);
115 assert!(result.is_empty());
116 }
117
118 #[test]
120 fn test_top_level_change() {
121 let a = json!("Alice");
122 let b = json!("Bob");
123 let result = deep_diff(&a, &b);
124 assert_eq!(
125 result,
126 vec![Difference {
127 path: "".to_string(),
128 before: Some(json!("Alice")),
129 after: Some(json!("Bob")),
130 }]
131 );
132 }
133
134 #[test]
140 fn test_array_number_change() {
141 let a = json!([1, 2]);
142 let b = json!([1, 3]);
143 let result = deep_diff(&a, &b);
144 assert_eq!(
145 result,
146 vec![Difference {
147 path: "[1]".to_string(),
148 before: Some(json!(2)),
149 after: Some(json!(3)),
150 }]
151 );
152 }
153
154 #[test]
156 fn test_array_string_change() {
157 let a = json!(["Alice", "Bob"]);
158 let b = json!(["Alice", "Hob"]);
159 let result = deep_diff(&a, &b);
160 assert_eq!(
161 result,
162 vec![Difference {
163 path: "[1]".to_string(),
164 before: Some(json!("Bob")),
165 after: Some(json!("Hob")),
166 }]
167 );
168 }
169
170 #[test]
172 fn test_array_unequal_length() {
173 let a = json!([1, 2]);
174 let b = json!([1]);
175 let result = deep_diff(&a, &b);
176 assert_eq!(
177 result,
178 vec![Difference {
179 path: "[1]".to_string(),
180 before: Some(json!(2)),
181 after: Some(Value::Null),
182 }]
183 );
184 }
185
186 #[test]
192 fn test_compare_map_same() {
193 let a = json!({"name": "Bob", "age": 25});
194 let result = deep_diff(&a, &a);
195 assert!(result.is_empty());
196 }
197
198 #[test]
200 fn test_compare_map_different() {
201 let a = json!({"name": "Bob", "age": 25});
202 let b = json!({"name": "Bob", "age": 26});
203 let result = deep_diff(&a, &b);
204 assert_eq!(
205 result,
206 vec![Difference {
207 path: "age".to_string(),
208 before: Some(json!(25)),
209 after: Some(json!(26)),
210 }]
211 );
212 }
213
214 #[test]
220 fn test_deep_nested_object() {
221 let a = json!({ "person": { "name": { "first": "Alice" } } });
222 let b = json!({ "person": { "name": { "first": "Bob" } } });
223 let result = deep_diff(&a, &b);
224 assert_eq!(
225 result,
226 vec![Difference {
227 path: "person.name.first".to_string(),
228 before: Some(json!("Alice")),
229 after: Some(json!("Bob")),
230 }]
231 );
232 }
233
234 #[test]
236 fn test_deep_nested_array() {
237 let a = json!({ "person": { "name": { "first": [1, 2, 3] } } });
238 let b = json!({ "person": { "name": { "first": [1, 2, 4] } } });
239 let result = deep_diff(&a, &b);
240 assert_eq!(
241 result,
242 vec![Difference {
243 path: "person.name.first[2]".to_string(),
244 before: Some(json!(3)),
245 after: Some(json!(4)),
246 }]
247 );
248 }
249}