1use serde::Serialize;
2use serde_json::Value;
3
4#[derive(Debug, PartialEq, Serialize)]
5pub enum DiffResult {
6 Added(String, Value),
7 Removed(String, Value),
8 Modified(String, Value, Value),
9 TypeChanged(String, Value, Value),
10}
11
12pub fn diff(
13 v1: &Value,
14 v2: &Value,
15) -> Vec<DiffResult> {
16 let mut results = Vec::new();
17
18 if v1 != v2 {
20 let type_match = match (v1, v2) {
21 (Value::Null, Value::Null) => true,
22 (Value::Bool(_), Value::Bool(_)) => true,
23 (Value::Number(_), Value::Number(_)) => true,
24 (Value::String(_), Value::String(_)) => true,
25 (Value::Array(_), Value::Array(_)) => true,
26 (Value::Object(_), Value::Object(_)) => true,
27 _ => false,
28 };
29
30 if !type_match {
31 results.push(DiffResult::TypeChanged("".to_string(), v1.clone(), v2.clone()));
32 return results; } else if v1.is_object() && v2.is_object() {
34 diff_objects("", v1.as_object().unwrap(), v2.as_object().unwrap(), &mut results);
35 } else if v1.is_array() && v2.is_array() {
36 diff_arrays("", v1.as_array().unwrap(), v2.as_array().unwrap(), &mut results);
37 } else {
38 results.push(DiffResult::Modified("".to_string(), v1.clone(), v2.clone()));
40 return results;
41 }
42 }
43
44 results
45}
46
47fn diff_recursive(
48 path: &str,
49 v1: &Value,
50 v2: &Value,
51 results: &mut Vec<DiffResult>,
52) {
53 match (v1, v2) {
54 (Value::Object(map1), Value::Object(map2)) => {
55 diff_objects(path, map1, map2, results);
56 }
57 (Value::Array(arr1), Value::Array(arr2)) => {
58 diff_arrays(path, arr1, arr2, results);
59 }
60 _ => { }
61 }
62}
63
64fn diff_objects(
65 path: &str,
66 map1: &serde_json::Map<String, Value>,
67 map2: &serde_json::Map<String, Value>,
68 results: &mut Vec<DiffResult>,
69) {
70 for (key, value1) in map1 {
72 let current_path = if path.is_empty() { key.clone() } else { format!("{}.{}", path, key) };
73 match map2.get(key) {
74 Some(value2) => {
75 if value1.is_object() && value2.is_object() || value1.is_array() && value2.is_array() {
77 diff_recursive(¤t_path, value1, value2, results);
78 } else if value1 != value2 {
79 let type_match = match (value1, value2) {
80 (Value::Null, Value::Null) => true,
81 (Value::Bool(_), Value::Bool(_)) => true,
82 (Value::Number(_), Value::Number(_)) => true,
83 (Value::String(_), Value::String(_)) => true,
84 (Value::Array(_), Value::Array(_)) => true,
85 (Value::Object(_), Value::Object(_)) => true,
86 _ => false,
87 };
88
89 if !type_match {
90 results.push(DiffResult::TypeChanged(current_path, value1.clone(), value2.clone()));
91 } else {
92 results.push(DiffResult::Modified(current_path, value1.clone(), value2.clone()));
93 }
94 }
95 }
96 None => {
97 results.push(DiffResult::Removed(current_path, value1.clone()));
98 }
99 }
100 }
101
102 for (key, value2) in map2 {
104 if !map1.contains_key(key) {
105 let current_path = if path.is_empty() { key.clone() } else { format!("{}.{}", path, key) };
106 results.push(DiffResult::Added(current_path, value2.clone()));
107 }
108 }
109}
110
111fn diff_arrays(
112 path: &str,
113 arr1: &Vec<Value>,
114 arr2: &Vec<Value>,
115 results: &mut Vec<DiffResult>,
116) {
117 let max_len = arr1.len().max(arr2.len());
118 for i in 0..max_len {
119 let current_path = format!("{}[{}]", path, i);
120 match (arr1.get(i), arr2.get(i)) {
121 (Some(val1), Some(val2)) => {
122 if val1.is_object() && val2.is_object() || val1.is_array() && val2.is_array() {
124 diff_recursive(¤t_path, val1, val2, results);
125 } else if val1 != val2 {
126 let type_match = match (val1, val2) {
127 (Value::Null, Value::Null) => true,
128 (Value::Bool(_), Value::Bool(_)) => true,
129 (Value::Number(_), Value::Number(_)) => true,
130 (Value::String(_), Value::String(_)) => true,
131 (Value::Array(_), Value::Array(_)) => true,
132 (Value::Object(_), Value::Object(_)) => true,
133 _ => false,
134 };
135
136 if !type_match {
137 results.push(DiffResult::TypeChanged(current_path, val1.clone(), val2.clone()));
138 } else {
139 results.push(DiffResult::Modified(current_path, val1.clone(), val2.clone()));
140 }
141 }
142 }
143 (Some(val1), None) => {
144 results.push(DiffResult::Removed(current_path, val1.clone()));
145 }
146 (None, Some(val2)) => {
147 results.push(DiffResult::Added(current_path, val2.clone()));
148 }
149 (None, None) => { }
150 }
151 }
152}
153
154pub fn value_type_name(value: &Value) -> &str {
155 match value {
156 Value::Null => "Null",
157 Value::Bool(_) => "Boolean",
158 Value::Number(_) => "Number",
159 Value::String(_) => "String",
160 Value::Array(_) => "Array",
161 Value::Object(_) => "Object",
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use serde_json::json;
169
170 #[test]
171 fn test_diff_no_changes() {
172 let v1 = json!({ "a": 1, "b": 2 });
173 let v2 = json!({ "a": 1, "b": 2 });
174 let differences = diff(&v1, &v2);
175 assert!(differences.is_empty());
176 }
177
178 #[test]
179 fn test_diff_value_modified() {
180 let v1 = json!({ "a": 1, "b": 2 });
181 let v2 = json!({ "a": 1, "b": 3 });
182 let differences = diff(&v1, &v2);
183 assert_eq!(differences.len(), 1);
184 assert_eq!(differences[0], DiffResult::Modified("b".to_string(), json!(2), json!(3)));
185 }
186
187 #[test]
188 fn test_diff_key_added() {
189 let v1 = json!({ "a": 1 });
190 let v2 = json!({ "a": 1, "b": 2 });
191 let differences = diff(&v1, &v2);
192 assert_eq!(differences.len(), 1);
193 assert_eq!(differences[0], DiffResult::Added("b".to_string(), json!(2)));
194 }
195
196 #[test]
197 fn test_diff_key_removed() {
198 let v1 = json!({ "a": 1, "b": 2 });
199 let v2 = json!({ "a": 1 });
200 let differences = diff(&v1, &v2);
201 assert_eq!(differences.len(), 1);
202 assert_eq!(differences[0], DiffResult::Removed("b".to_string(), json!(2)));
203 }
204
205 #[test]
206 fn test_diff_type_changed() {
207 let v1 = json!({ "a": 1 });
208 let v2 = json!({ "a": "1" });
209 let differences = diff(&v1, &v2);
210 assert_eq!(differences.len(), 1);
211 assert_eq!(differences[0], DiffResult::TypeChanged("a".to_string(), json!(1), json!("1")));
212 }
213
214 #[test]
215 fn test_diff_nested_object_modified() {
216 let v1 = json!({ "a": { "b": 1 } });
217 let v2 = json!({ "a": { "b": 2 } });
218 let differences = diff(&v1, &v2);
219 assert_eq!(differences.len(), 1);
220 assert_eq!(differences[0], DiffResult::Modified("a.b".to_string(), json!(1), json!(2)));
221 }
222
223 #[test]
224 fn test_diff_array_element_added() {
225 let v1 = json!([1, 2]);
226 let v2 = json!([1, 2, 3]);
227 let differences = diff(&v1, &v2);
228 assert_eq!(differences.len(), 1);
229 assert_eq!(differences[0], DiffResult::Added("[2]".to_string(), json!(3)));
230 }
231
232 #[test]
233 fn test_diff_array_element_removed() {
234 let v1 = json!([1, 2, 3]);
235 let v2 = json!([1, 2]);
236 let differences = diff(&v1, &v2);
237 assert_eq!(differences.len(), 1);
238 assert_eq!(differences[0], DiffResult::Removed("[2]".to_string(), json!(3)));
239 }
240
241 #[test]
242 fn test_diff_array_element_modified() {
243 let v1 = json!([1, 2, 3]);
244 let v2 = json!([1, 2, 4]);
245 let differences = diff(&v1, &v2);
246 assert_eq!(differences.len(), 1);
247 assert_eq!(differences[0], DiffResult::Modified("[2]".to_string(), json!(3), json!(4)));
248 }
249
250 #[test]
251 fn test_diff_nested_array_element_modified() {
252 let v1 = json!({ "a": [1, 2, 3] });
253 let v2 = json!({ "a": [1, 2, 4] });
254 let differences = diff(&v1, &v2);
255 assert_eq!(differences.len(), 1);
256 assert_eq!(differences[0], DiffResult::Modified("a[2]".to_string(), json!(3), json!(4)));
257 }
258
259 #[test]
260 fn test_diff_root_type_changed() {
261 let v1 = json!(1);
262 let v2 = json!("1");
263 let differences = diff(&v1, &v2);
264 assert_eq!(differences.len(), 1);
265 assert_eq!(differences[0], DiffResult::TypeChanged("".to_string(), json!(1), json!("1")));
266 }
267
268 #[test]
269 fn test_diff_nested_object_and_array() {
270 let v1 = json!({
271 "config": {
272 "users": [
273 {"id": 1, "name": "Alice"},
274 {"id": 2, "name": "Bob"}
275 ],
276 "settings": {"theme": "dark"}
277 }
278 });
279 let v2 = json!({
280 "config": {
281 "users": [
282 {"id": 1, "name": "Alice"},
283 {"id": 2, "name": "Robert"},
284 {"id": 3, "name": "Charlie"}
285 ],
286 "settings": {"theme": "light", "font_size": 12}
287 }
288 });
289 let differences = diff(&v1, &v2);
290 assert_eq!(differences.len(), 4);
291 assert!(differences.contains(&DiffResult::Modified("config.users[1].name".to_string(), json!("Bob"), json!("Robert"))));
292 assert!(differences.contains(&DiffResult::Added("config.users[2]".to_string(), json!({"id": 3, "name": "Charlie"}))));
293 assert!(differences.contains(&DiffResult::Modified("config.settings.theme".to_string(), json!("dark"), json!("light"))));
294 assert!(differences.contains(&DiffResult::Added("config.settings.font_size".to_string(), json!(12))));
295 }
296
297 #[test]
298 fn test_diff_empty_objects_and_arrays() {
299 let v1 = json!({
300 "empty_obj": {},
301 "empty_arr": [],
302 "data": "value"
303 });
304 let v2 = json!({
305 "empty_obj": {},
306 "empty_arr": [],
307 "data": "new_value"
308 });
309 let differences = diff(&v1, &v2);
310 assert_eq!(differences.len(), 1);
311 assert_eq!(differences[0], DiffResult::Modified("data".to_string(), json!("value"), json!("new_value")));
312 }
313
314 #[test]
315 fn test_diff_root_array_changes() {
316 let v1 = json!([
317 {"id": 1},
318 {"id": 2}
319 ]);
320 let v2 = json!([
321 {"id": 1},
322 {"id": 3},
323 {"id": 4}
324 ]);
325 let differences = diff(&v1, &v2);
326 assert_eq!(differences.len(), 2);
327 assert!(differences.contains(&DiffResult::Modified("[1].id".to_string(), json!(2), json!(3))));
328 assert!(differences.contains(&DiffResult::Added("[2]".to_string(), json!({"id": 4}))));
329 }
330}