diff --git a/jsoncomparison/compare.py b/jsoncomparison/compare.py index 3b41caa..305e74a 100644 --- a/jsoncomparison/compare.py +++ b/jsoncomparison/compare.py @@ -1,5 +1,6 @@ import copy import json +import math from typing import Optional from .config import Config @@ -101,6 +102,12 @@ def _str_diff(cls, e, a): def _float_diff(self, e, a): if a == e: return NO_DIFF + + if self._can_percent_diff(): + p = self._float_percent_diff() + if math.isclose(a, e, rel_tol=p): + return NO_DIFF + if self._can_rounded_float(): p = self._float_precision() e, a = round(e, p), round(a, p) @@ -112,10 +119,18 @@ def _can_rounded_float(self): p = self._float_precision() return type(p) is int + def _can_percent_diff(self): + p = self._float_percent_diff() + return type(p) is float + def _float_precision(self): path = 'types.float.allow_round' return self._config.get(path) + def _float_percent_diff(self): + path = 'types.float.allow_percent_diff' + return self._config.get(path) + def _dict_diff(self, e, a): d = {} for k in e: @@ -149,7 +164,9 @@ def _list_content_diff(self, e, a): if v in a: continue t = type(v) - if t in (int, str, bool, float): + if t is float: + d[i] = self._min_diff(v, a, self._float_diff) + if t in (int, str, bool): d[i] = ValueNotFound(v, None).explain() elif t is dict: d[i] = self._min_diff(v, a, self._dict_diff) @@ -177,7 +194,6 @@ def _min_diff(cls, e, lst, method): dd = method(e, v) if len(dd) <= len(d): d = dd - break return d @classmethod diff --git a/jsoncomparison/ignore.py b/jsoncomparison/ignore.py index e46c38e..96fb340 100644 --- a/jsoncomparison/ignore.py +++ b/jsoncomparison/ignore.py @@ -6,6 +6,9 @@ class Ignore(ABC): @classmethod def transform(cls, obj, rules): + if obj is None: + return None + t = type(rules) if t is dict: return cls._apply_dictable_rule(obj, rules) diff --git a/tests/data/compare/config.json b/tests/data/compare/config.json index a2c62d2..1c8d5f2 100644 --- a/tests/data/compare/config.json +++ b/tests/data/compare/config.json @@ -1,7 +1,8 @@ { "types": { "float": { - "allow_round": 2 + "allow_round": 2, + "allow_percent_diff": 0.05 }, "list": { "check_length": true @@ -11,4 +12,4 @@ "console": false, "file": false } -} \ No newline at end of file +} diff --git a/tests/test_compare.py b/tests/test_compare.py index d4d5026..36cd65f 100644 --- a/tests/test_compare.py +++ b/tests/test_compare.py @@ -41,6 +41,9 @@ def test_compare_float(self): diff = self.compare.check(1.23456, 1.23) self.assertEqual(diff, NO_DIFF) + diff = self.compare.check(100.0, 105.0) + self.assertEqual(diff, NO_DIFF) + diff = self.compare.check(1.2, 1.3) self.assertEqual(diff, ValuesNotEqual(1.2, 1.3).explain()) @@ -126,7 +129,7 @@ def test_simple_list_with_dicts(self): self.assertEqual( diff, { '_content': { - 0: {'a': ValuesNotEqual('xxx', 'zzz').explain()}, + 0: {'a': ValuesNotEqual('xxx', 'yyy').explain()}, }, }, ) @@ -152,8 +155,8 @@ def test_list_with_dicts(self): self.assertEqual( diff, { '_content': { - 1: {'a': ValuesNotEqual('iii', 'zzz').explain()}, - 3: {'a': ValuesNotEqual('jjj', 'zzz').explain()}, + 1: {'a': ValuesNotEqual('iii', 'eee').explain()}, + 3: {'a': ValuesNotEqual('jjj', 'eee').explain()}, }, }, ) @@ -175,7 +178,7 @@ def test_list_with_dicts_with_duplicates(self): self.assertEqual( diff, { '_content': { - 3: {'a': ValuesNotEqual('jjj', 'zzz').explain()}, + 3: {'a': ValuesNotEqual('jjj', 'iii').explain()}, }, }, )