serde_json_assert/
diff.rs

1use crate::core_ext::{Indent, Indexes};
2use crate::{ArraySortingMode, CompareMode, Config, FloatCompareMode, NumericMode};
3use float_cmp::{ApproxEq, F64Margin, FloatMargin};
4use serde_json::Value;
5use std::{collections::HashSet, fmt};
6
7pub(crate) fn diff<'a>(
8    lhs: &'a Value,
9    rhs: &'a Value,
10    config: &'a Config,
11) -> Vec<DifferenceRef<'a>> {
12    let mut acc = vec![];
13    diff_with(lhs, rhs, config, PathRef::Root, &mut acc);
14    acc
15}
16
17fn diff_with<'a>(
18    lhs: &'a Value,
19    rhs: &'a Value,
20    config: &'a Config,
21    path: PathRef<'a>,
22    acc: &mut Vec<DifferenceRef<'a>>,
23) {
24    let mut folder = DiffFolder {
25        rhs,
26        path,
27        acc,
28        config,
29    };
30
31    fold_json(lhs, &mut folder);
32}
33
34#[derive(Debug)]
35struct DiffFolder<'a, 'b> {
36    rhs: &'a Value,
37    path: PathRef<'a>,
38    acc: &'b mut Vec<DifferenceRef<'a>>,
39    config: &'a Config,
40}
41
42macro_rules! direct_compare {
43    ($name:ident) => {
44        fn $name(&mut self, lhs: &'a Value) {
45            if self.rhs != lhs {
46                self.acc.push(DifferenceRef {
47                    lhs: Some(lhs),
48                    rhs: Some(&self.rhs),
49                    path: self.path.clone(),
50                    config: self.config.clone(),
51                });
52            }
53        }
54    };
55}
56
57impl<'a> DiffFolder<'a, '_> {
58    direct_compare!(on_null);
59    direct_compare!(on_bool);
60    direct_compare!(on_string);
61
62    fn on_number(&mut self, lhs: &'a Value) {
63        let is_equal = match self.config.numeric_mode {
64            NumericMode::Strict => self.eq_values(lhs, self.rhs),
65            NumericMode::AssumeFloat => match (lhs.as_f64(), self.rhs.as_f64()) {
66                (Some(lhs), Some(rhs)) => self.eq_floats(lhs, rhs),
67                (lhs, rhs) => lhs == rhs,
68            },
69        };
70        if !is_equal {
71            self.acc.push(DifferenceRef {
72                lhs: Some(lhs),
73                rhs: Some(self.rhs),
74                path: self.path.clone(),
75                config: self.config.clone(),
76            });
77        }
78    }
79
80    fn eq_values(&self, lhs: &Value, rhs: &Value) -> bool {
81        if lhs.is_f64() && rhs.is_f64() {
82            // `as_f64` must return a floating point value if `is_f64` returned true. The inverse
83            // relation is not guaranteed by serde_json.
84            self.eq_floats(
85                lhs.as_f64().expect("float value"),
86                rhs.as_f64().expect("float value"),
87            )
88        } else {
89            lhs == rhs
90        }
91    }
92
93    fn eq_floats(&self, lhs: f64, rhs: f64) -> bool {
94        if let FloatCompareMode::Epsilon(epsilon) = self.config.float_compare_mode {
95            lhs.approx_eq(rhs, F64Margin::default().epsilon(epsilon))
96        } else {
97            lhs == rhs
98        }
99    }
100    fn on_array_contains(&mut self, lhs: &'a Value) {
101        if let Some(rhs) = self.rhs.as_array() {
102            let lhs_array = lhs.as_array().unwrap();
103
104            let lhs_len = lhs_array.len();
105            let rhs_len = rhs.len();
106
107            if self.config.compare_mode == CompareMode::Strict && lhs_len != rhs_len {
108                self.acc.push(DifferenceRef {
109                    lhs: Some(lhs),
110                    rhs: Some(self.rhs),
111                    path: self.path.clone(),
112                    config: self.config.clone(),
113                });
114                return;
115            }
116
117            for rhs_item in rhs.iter() {
118                // For each rhs item (expected) count the number of times it matches with the rhs
119                // (expected) array.
120                let rhs_item_count = rhs
121                    .iter()
122                    .filter(|i| diff(rhs_item, i, self.config).is_empty())
123                    .count();
124                // Make sure that lhs (actual) has at least as many items matching the rhs
125                // (expected) item.
126                let lhs_matching_items_count = lhs_array
127                    .iter()
128                    .filter(|lhs_item| diff(lhs_item, rhs_item, self.config).is_empty())
129                    .count();
130                if lhs_matching_items_count < rhs_item_count {
131                    self.acc.push(DifferenceRef {
132                        lhs: Some(lhs),
133                        rhs: Some(self.rhs),
134                        path: self.path.clone(),
135                        config: self.config.clone(),
136                    });
137                    break;
138                }
139            }
140        } else {
141            self.acc.push(DifferenceRef {
142                lhs: Some(lhs),
143                rhs: Some(self.rhs),
144                path: self.path.clone(),
145                config: self.config.clone(),
146            });
147        }
148    }
149
150    fn on_array(&mut self, lhs: &'a Value) {
151        if self.config.array_sorting_mode == ArraySortingMode::Ignore {
152            return self.on_array_contains(lhs);
153        }
154
155        if let Some(rhs) = self.rhs.as_array() {
156            let lhs = lhs.as_array().unwrap();
157
158            match self.config.compare_mode {
159                CompareMode::Inclusive => {
160                    for (idx, rhs) in rhs.iter().enumerate() {
161                        let path = self.path.append(KeyRef::Idx(idx));
162
163                        if let Some(lhs) = lhs.get(idx) {
164                            diff_with(lhs, rhs, self.config, path, self.acc)
165                        } else {
166                            self.acc.push(DifferenceRef {
167                                lhs: None,
168                                rhs: Some(self.rhs),
169                                path,
170                                config: self.config.clone(),
171                            });
172                        }
173                    }
174                }
175                CompareMode::Strict => {
176                    let all_keys = rhs
177                        .indexes()
178                        .into_iter()
179                        .chain(lhs.indexes())
180                        .collect::<HashSet<_>>();
181                    for key in all_keys {
182                        let path = self.path.append(KeyRef::Idx(key));
183
184                        match (lhs.get(key), rhs.get(key)) {
185                            (Some(lhs), Some(rhs)) => {
186                                diff_with(lhs, rhs, self.config, path, self.acc);
187                            }
188                            (None, Some(rhs)) => {
189                                self.acc.push(DifferenceRef {
190                                    lhs: None,
191                                    rhs: Some(rhs),
192                                    path,
193                                    config: self.config.clone(),
194                                });
195                            }
196                            (Some(lhs), None) => {
197                                self.acc.push(DifferenceRef {
198                                    lhs: Some(lhs),
199                                    rhs: None,
200                                    path,
201                                    config: self.config.clone(),
202                                });
203                            }
204                            (None, None) => {
205                                unreachable!("at least one of the maps should have the key")
206                            }
207                        }
208                    }
209                }
210            }
211        } else {
212            self.acc.push(DifferenceRef {
213                lhs: Some(lhs),
214                rhs: Some(self.rhs),
215                path: self.path.clone(),
216                config: self.config.clone(),
217            });
218        }
219    }
220
221    fn on_object(&mut self, lhs: &'a Value) {
222        if let Some(rhs) = self.rhs.as_object() {
223            let lhs = lhs.as_object().unwrap();
224
225            match self.config.compare_mode {
226                CompareMode::Inclusive => {
227                    for (key, rhs) in rhs.iter() {
228                        let path = self.path.append(KeyRef::Field(key));
229
230                        if let Some(lhs) = lhs.get(key) {
231                            diff_with(lhs, rhs, self.config, path, self.acc)
232                        } else {
233                            self.acc.push(DifferenceRef {
234                                lhs: None,
235                                rhs: Some(self.rhs),
236                                path,
237                                config: self.config.clone(),
238                            });
239                        }
240                    }
241                }
242                CompareMode::Strict => {
243                    let all_keys = rhs.keys().chain(lhs.keys()).collect::<HashSet<_>>();
244                    for key in all_keys {
245                        let path = self.path.append(KeyRef::Field(key));
246
247                        match (lhs.get(key), rhs.get(key)) {
248                            (Some(lhs), Some(rhs)) => {
249                                diff_with(lhs, rhs, self.config, path, self.acc);
250                            }
251                            (None, Some(rhs)) => {
252                                self.acc.push(DifferenceRef {
253                                    lhs: None,
254                                    rhs: Some(rhs),
255                                    path,
256                                    config: self.config.clone(),
257                                });
258                            }
259                            (Some(lhs), None) => {
260                                self.acc.push(DifferenceRef {
261                                    lhs: Some(lhs),
262                                    rhs: None,
263                                    path,
264                                    config: self.config.clone(),
265                                });
266                            }
267                            (None, None) => {
268                                unreachable!("at least one of the maps should have the key")
269                            }
270                        }
271                    }
272                }
273            }
274        } else {
275            self.acc.push(DifferenceRef {
276                lhs: Some(lhs),
277                rhs: Some(self.rhs),
278                path: self.path.clone(),
279                config: self.config.clone(),
280            });
281        }
282    }
283}
284
285/// Represents a difference between two JSON values.
286#[derive(Debug, PartialEq, Clone)]
287pub struct Difference {
288    path: Path,
289    lhs: Option<Value>,
290    rhs: Option<Value>,
291    config: Config,
292}
293
294impl Difference {
295    /// Returns the path to the difference.
296    pub fn path(&self) -> &Path {
297        &self.path
298    }
299
300    /// Get the left-hand side, or "actual", value of the difference.
301    pub fn actual(&self) -> &Option<Value> {
302        &self.lhs
303    }
304
305    /// Get the right-hand side, or "expected", value of the difference.
306    pub fn expected(&self) -> &Option<Value> {
307        &self.rhs
308    }
309
310    /// Returns the configuration used to generate this difference.
311    pub fn config(&self) -> &Config {
312        &self.config
313    }
314}
315
316impl<'a> From<DifferenceRef<'a>> for Difference {
317    fn from(diff: DifferenceRef<'a>) -> Self {
318        Difference {
319            path: Path::from(diff.path),
320            lhs: diff.lhs.cloned(),
321            rhs: diff.rhs.cloned(),
322            config: diff.config.clone(),
323        }
324    }
325}
326
327#[derive(Debug, PartialEq)]
328pub(crate) struct DifferenceRef<'a> {
329    path: PathRef<'a>,
330    lhs: Option<&'a Value>,
331    rhs: Option<&'a Value>,
332    config: Config,
333}
334
335impl fmt::Display for DifferenceRef<'_> {
336    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
337        let json_to_string = |json: &Value| serde_json::to_string_pretty(json).unwrap();
338
339        match (&self.config.compare_mode, &self.lhs, &self.rhs) {
340            (CompareMode::Inclusive, Some(actual), Some(expected)) => {
341                writeln!(f, "json atoms at path \"{}\" are not equal:", self.path)?;
342                writeln!(f, "    expected:")?;
343                writeln!(f, "{}", json_to_string(expected).indent(8))?;
344                writeln!(f, "    actual:")?;
345                write!(f, "{}", json_to_string(actual).indent(8))?;
346            }
347            (CompareMode::Inclusive, None, Some(_expected)) => {
348                write!(
349                    f,
350                    "json atom at path \"{}\" is missing from actual",
351                    self.path
352                )?;
353            }
354            (CompareMode::Inclusive, Some(_actual), None) => {
355                unreachable!("stuff missing actual wont produce an error")
356            }
357            (CompareMode::Inclusive, None, None) => unreachable!("can't both be missing"),
358
359            (CompareMode::Strict, Some(lhs), Some(rhs)) => {
360                writeln!(f, "json atoms at path \"{}\" are not equal:", self.path)?;
361                writeln!(f, "    lhs:")?;
362                writeln!(f, "{}", json_to_string(lhs).indent(8))?;
363                writeln!(f, "    rhs:")?;
364                write!(f, "{}", json_to_string(rhs).indent(8))?;
365            }
366            (CompareMode::Strict, None, Some(_)) => {
367                write!(f, "json atom at path \"{}\" is missing from lhs", self.path)?;
368            }
369            (CompareMode::Strict, Some(_), None) => {
370                write!(f, "json atom at path \"{}\" is missing from rhs", self.path)?;
371            }
372            (CompareMode::Strict, None, None) => unreachable!("can't both be missing"),
373        }
374
375        Ok(())
376    }
377}
378
379/// Represents a path to a JSON value in a tree structure.
380#[derive(Debug, Clone, PartialEq)]
381pub enum Path {
382    /// The root of the JSON tree.
383    Root,
384    /// A path to a JSON object or array.
385    Keys(Vec<Key>),
386}
387
388impl<'a> From<PathRef<'a>> for Path {
389    fn from(path: PathRef<'a>) -> Self {
390        match path {
391            PathRef::Root => Path::Root,
392            PathRef::Keys(keys) => Path::Keys(keys.into_iter().map(Key::from).collect()),
393        }
394    }
395}
396
397#[derive(Debug, Clone, PartialEq)]
398enum PathRef<'a> {
399    Root,
400    Keys(Vec<KeyRef<'a>>),
401}
402
403impl<'a> PathRef<'a> {
404    fn append(&self, next: KeyRef<'a>) -> PathRef<'a> {
405        match self {
406            PathRef::Root => PathRef::Keys(vec![next]),
407            PathRef::Keys(list) => {
408                let mut copy = list.clone();
409                copy.push(next);
410                PathRef::Keys(copy)
411            }
412        }
413    }
414}
415
416impl fmt::Display for PathRef<'_> {
417    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
418        match self {
419            PathRef::Root => write!(f, "(root)"),
420            PathRef::Keys(keys) => {
421                for key in keys {
422                    write!(f, "{}", key)?;
423                }
424                Ok(())
425            }
426        }
427    }
428}
429
430/// Represents a key in a JSON object or an index in a JSON array.
431#[derive(Debug, Clone, PartialEq, Eq)]
432pub enum Key {
433    /// An index in a JSON array.
434    Idx(usize),
435    /// A field in a JSON object.
436    Field(String),
437}
438
439impl<'a> From<KeyRef<'a>> for Key {
440    fn from(key: KeyRef<'a>) -> Self {
441        match key {
442            KeyRef::Idx(idx) => Key::Idx(idx),
443            KeyRef::Field(field) => Key::Field(field.to_owned()),
444        }
445    }
446}
447
448#[derive(Debug, Copy, Clone, PartialEq)]
449enum KeyRef<'a> {
450    Idx(usize),
451    Field(&'a str),
452}
453
454impl fmt::Display for KeyRef<'_> {
455    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
456        match self {
457            KeyRef::Idx(idx) => write!(f, "[{}]", idx),
458            KeyRef::Field(key) => write!(f, ".{}", key),
459        }
460    }
461}
462
463fn fold_json<'a>(json: &'a Value, folder: &mut DiffFolder<'a, '_>) {
464    match json {
465        Value::Null => folder.on_null(json),
466        Value::Bool(_) => folder.on_bool(json),
467        Value::Number(_) => folder.on_number(json),
468        Value::String(_) => folder.on_string(json),
469        Value::Array(_) => folder.on_array(json),
470        Value::Object(_) => folder.on_object(json),
471    }
472}
473
474#[cfg(test)]
475mod test {
476    #[allow(unused_imports)]
477    use super::*;
478    use serde_json::json;
479
480    #[test]
481    fn test_diffing_leaf_json() {
482        let config = Config::new(CompareMode::Inclusive);
483        let diffs = diff(&json!(null), &json!(null), &config);
484        assert_eq!(diffs, vec![]);
485
486        let diffs = diff(&json!(false), &json!(false), &config);
487        assert_eq!(diffs, vec![]);
488
489        let diffs = diff(&json!(true), &json!(true), &config);
490        assert_eq!(diffs, vec![]);
491
492        let diffs = diff(&json!(false), &json!(true), &config);
493        assert_eq!(diffs.len(), 1);
494
495        let diffs = diff(&json!(true), &json!(false), &config);
496        assert_eq!(diffs.len(), 1);
497
498        let actual = json!(1);
499        let expected = json!(1);
500        let diffs = diff(&actual, &expected, &config);
501        assert_eq!(diffs, vec![]);
502
503        let actual = json!(2);
504        let expected = json!(1);
505        let diffs = diff(&actual, &expected, &config);
506        assert_eq!(diffs.len(), 1);
507
508        let actual = json!(1);
509        let expected = json!(2);
510        let diffs = diff(&actual, &expected, &config);
511        assert_eq!(diffs.len(), 1);
512
513        let actual = json!(1.0);
514        let expected = json!(1.0);
515        let diffs = diff(&actual, &expected, &config);
516        assert_eq!(diffs, vec![]);
517
518        let actual = json!(1);
519        let expected = json!(1.0);
520        let diffs = diff(&actual, &expected, &config);
521        assert_eq!(diffs.len(), 1);
522
523        let actual = json!(1.0);
524        let expected = json!(1);
525        let diffs = diff(&actual, &expected, &config);
526        assert_eq!(diffs.len(), 1);
527
528        let config_assume_float = config.numeric_mode(NumericMode::AssumeFloat);
529
530        let actual = json!(1);
531        let expected = json!(1.0);
532        let diffs = diff(&actual, &expected, &config_assume_float);
533        assert_eq!(diffs, vec![]);
534
535        let actual = json!(1.0);
536        let expected = json!(1);
537        let diffs = diff(&actual, &expected, &config_assume_float);
538        assert_eq!(diffs, vec![]);
539
540        let actual = json!(1.15);
541        let expected = json!(1);
542        let config = Config::new(CompareMode::Inclusive)
543            .numeric_mode(NumericMode::AssumeFloat)
544            .float_compare_mode(FloatCompareMode::Epsilon(0.2));
545        let diffs = diff(&actual, &expected, &config);
546        assert_eq!(diffs, vec![]);
547
548        let actual = json!(1.25);
549        let expected = json!(1);
550        let config = Config::new(CompareMode::Inclusive)
551            .numeric_mode(NumericMode::AssumeFloat)
552            .float_compare_mode(FloatCompareMode::Epsilon(0.2));
553
554        let diffs = diff(&actual, &expected, &config);
555        assert_eq!(diffs.len(), 1);
556
557        let actual = json!(2);
558        let expected = json!(1);
559        let config =
560            Config::new(CompareMode::Inclusive).float_compare_mode(FloatCompareMode::Epsilon(2.0));
561
562        let diffs = diff(&actual, &expected, &config);
563        assert_eq!(diffs.len(), 1);
564    }
565
566    #[test]
567    fn test_diffing_array() {
568        let config = Config::new(CompareMode::Inclusive);
569        // empty
570        let actual = json!([]);
571        let expected = json!([]);
572        let diffs = diff(&actual, &expected, &config);
573        assert_eq!(diffs, vec![]);
574
575        let actual = json!([1]);
576        let expected = json!([]);
577        let diffs = diff(&actual, &expected, &config);
578        assert_eq!(diffs.len(), 0);
579
580        let actual = json!([]);
581        let expected = json!([1]);
582        let diffs = diff(&actual, &expected, &config);
583        assert_eq!(diffs.len(), 1);
584
585        // eq
586        let actual = json!([1]);
587        let expected = json!([1]);
588        let diffs = diff(&actual, &expected, &config);
589        assert_eq!(diffs, vec![]);
590
591        // actual longer
592        let actual = json!([1, 2]);
593        let expected = json!([1]);
594        let diffs = diff(&actual, &expected, &config);
595        assert_eq!(diffs, vec![]);
596
597        // expected longer
598        let actual = json!([1]);
599        let expected = json!([1, 2]);
600        let diffs = diff(&actual, &expected, &config);
601        assert_eq!(diffs.len(), 1);
602
603        // eq length but different
604        let actual = json!([1, 3]);
605        let expected = json!([1, 2]);
606        let diffs = diff(&actual, &expected, &config);
607        assert_eq!(diffs.len(), 1);
608
609        // different types
610        let actual = json!(1);
611        let expected = json!([1]);
612        let diffs = diff(&actual, &expected, &config);
613        assert_eq!(diffs.len(), 1);
614
615        let actual = json!([1]);
616        let expected = json!(1);
617        let diffs = diff(&actual, &expected, &config);
618        assert_eq!(diffs.len(), 1);
619    }
620
621    #[test]
622    fn test_array_strict() {
623        let config = Config::new(CompareMode::Strict);
624        let actual = json!([]);
625        let expected = json!([]);
626        let diffs = diff(&actual, &expected, &config);
627        assert_eq!(diffs.len(), 0);
628
629        let actual = json!([1, 2]);
630        let expected = json!([1, 2]);
631        let diffs = diff(&actual, &expected, &config);
632        assert_eq!(diffs.len(), 0);
633
634        let actual = json!([1]);
635        let expected = json!([1, 2]);
636        let diffs = diff(&actual, &expected, &config);
637        assert_eq!(diffs.len(), 1);
638
639        let actual = json!([1, 2]);
640        let expected = json!([1]);
641        let diffs = diff(&actual, &expected, &config);
642        assert_eq!(diffs.len(), 1);
643    }
644
645    #[test]
646    fn test_object() {
647        let config = Config::new(CompareMode::Inclusive);
648        let actual = json!({});
649        let expected = json!({});
650        let diffs = diff(&actual, &expected, &config);
651        assert_eq!(diffs, vec![]);
652
653        let actual = json!({ "a": 1 });
654        let expected = json!({ "a": 1 });
655        let diffs = diff(&actual, &expected, &config);
656        assert_eq!(diffs, vec![]);
657
658        let actual = json!({ "a": 1, "b": 123 });
659        let expected = json!({ "a": 1 });
660        let diffs = diff(&actual, &expected, &config);
661        assert_eq!(diffs, vec![]);
662
663        let actual = json!({ "a": 1 });
664        let expected = json!({ "b": 1 });
665        let diffs = diff(&actual, &expected, &config);
666        assert_eq!(diffs.len(), 1);
667
668        let actual = json!({ "a": 1 });
669        let expected = json!({ "a": 2 });
670        let diffs = diff(&actual, &expected, &config);
671        assert_eq!(diffs.len(), 1);
672
673        let actual = json!({ "a": { "b": true } });
674        let expected = json!({ "a": {} });
675        let diffs = diff(&actual, &expected, &config);
676        assert_eq!(diffs, vec![]);
677    }
678
679    #[test]
680    fn test_object_strict() {
681        let config = Config::new(CompareMode::Strict);
682        let lhs = json!({});
683        let rhs = json!({ "a": 1 });
684        let diffs = diff(&lhs, &rhs, &config);
685        assert_eq!(diffs.len(), 1);
686
687        let lhs = json!({ "a": 1 });
688        let rhs = json!({});
689        let diffs = diff(&lhs, &rhs, &config);
690        assert_eq!(diffs.len(), 1);
691
692        let json = json!({ "a": 1 });
693        let diffs = diff(&json, &json, &config);
694        assert_eq!(diffs, vec![]);
695    }
696}