deep_diff/
lib.rs

1//! A small crate to deeply diff `serde_json::Value` trees.
2//!
3//! # Example
4//!
5//! ```rust
6//! use deep_diff::{deep_diff, Difference};
7//! use serde_json::json;
8//!
9//! let a = json!({"name": "Alice"});
10//! let b = json!({"name": "Bob"});
11//! let diffs = deep_diff(&a, &b);
12//! assert_eq!(diffs[0].path, "name");
13//!
14
15use serde_json::Value;
16
17#[derive(Debug, PartialEq, Eq, Clone)]
18pub struct Difference {
19    /// The path to the value that changed (e.g., `"name"` or `"items[0]"`).
20    pub path: String,
21    /// The value before the change (in the first input).
22    pub before: Option<Value>,
23    /// The value after the change (in the second input).
24    pub after: Option<Value>,
25}
26
27// Determines if two json types are equivalent
28fn 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        // Deals with primitive types
43        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        // Deals with arrays
53        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        // Deals with objects
62        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
98/// Computes the differences between two JSON values.
99pub 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 that no differences are found when comparing identical primitive JSON values.
111    #[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 difference at the top-level between two string JSON values.
119    #[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    // ======================
135    // Array Comparison Tests
136    // ======================
137
138    /// Test difference between numeric elements in an array.
139    #[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 difference between string elements in an array.
155    #[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 difference when arrays are of unequal length.
171    #[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    // ======================
187    // Object Comparison Tests
188    // ======================
189
190    /// Test that no differences are found when comparing identical maps.
191    #[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 difference in a single field of a map.
199    #[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    // ======================
215    // Deep Nested JSON Tests
216    // ======================
217
218    /// Test difference in deeply nested object fields.
219    #[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 difference in deeply nested array elements.
235    #[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}