Skip to content

Commit ad9cdf2

Browse files
committed
DeepDiff.to_dict() was not useful when the original view was 'tree' — it returned a
dict containing opaque DiffLevel objects rather than a readable Python dictionary. The workaround was to_dict(view_override='text'), but that was locked to whatever verbose_level was set at init time (default 1), meaning you couldn't get the full detail that the tree view had access to. Change: Replaced the view_override parameter with verbose_level on both to_dict() and to_json(). These methods now always produce a text-view dictionary — an actual usable Python dict. The default verbosity is context-aware: - If the original view is 'text': uses the verbose_level from initialization (preserving existing behavior). - If the original view is 'tree': defaults to verbose_level=2 (the most detailed output), since tree-view users chose that view for its richness. In both cases, you can override the verbosity explicitly: to_dict(verbose_level=0), to_dict(verbose_level=1), etc.
1 parent bad0ee2 commit ad9cdf2

8 files changed

Lines changed: 126 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# DeepDiff Change log
22

3+
- v8-7-0
4+
- Support for python 3.14
5+
- `to_dict()` and `to_json()` now accept a `verbose_level` parameter and always return a usable text-view dict. When the original view is `'tree'`, they default to `verbose_level=2` for full detail. The old `view_override` parameter is removed.
6+
37
- v8-6-1
48
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
59

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Please check the [ChangeLog](CHANGELOG.md) file for the detailed information.
2525

2626
DeepDiff 8-7-0
2727
- support for python 3.14
28+
- `to_dict()` and `to_json()` now accept a `verbose_level` parameter and always return a usable text-view dict. When the original view is `'tree'`, they default to `verbose_level=2` for full detail. The old `view_override` parameter is removed.
2829

2930
DeepDiff 8-6-1
3031
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).

deepdiff/diff.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,7 +1810,7 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree=
18101810
else:
18111811
self._diff_obj(level, parents_ids)
18121812

1813-
def _get_view_results(self, view):
1813+
def _get_view_results(self, view, verbose_level=None):
18141814
"""
18151815
Get the results based on the view
18161816
"""
@@ -1820,7 +1820,8 @@ def _get_view_results(self, view):
18201820
if view == TREE_VIEW:
18211821
pass
18221822
elif view == TEXT_VIEW:
1823-
result = TextResult(tree_results=self.tree, verbose_level=self.verbose_level)
1823+
effective_verbose_level = verbose_level if verbose_level is not None else self.verbose_level
1824+
result = TextResult(tree_results=self.tree, verbose_level=effective_verbose_level)
18241825
result.remove_empty_keys()
18251826
elif view == DELTA_VIEW:
18261827
result = self._to_delta_dict(report_repetition_required=False)

deepdiff/serialization.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
strings,
2525
get_type,
2626
TEXT_VIEW,
27+
TREE_VIEW,
2728
np_float32,
2829
np_float64,
2930
np_int32,
@@ -171,7 +172,7 @@ def from_json_pickle(cls, value):
171172
except ImportError: # pragma: no cover. Json pickle is getting deprecated.
172173
logger.error('jsonpickle library needs to be installed in order to run from_json_pickle') # pragma: no cover. Json pickle is getting deprecated.
173174

174-
def to_json(self, default_mapping: Optional[dict]=None, force_use_builtin_json=False, **kwargs):
175+
def to_json(self, default_mapping: Optional[dict]=None, force_use_builtin_json=False, verbose_level: Optional[int]=None, **kwargs):
175176
"""
176177
Dump json of the text view.
177178
**Parameters**
@@ -186,6 +187,8 @@ def to_json(self, default_mapping: Optional[dict]=None, force_use_builtin_json=F
186187
When True, we use Python's builtin Json library for serialization,
187188
even if Orjson is installed.
188189
190+
verbose_level: int, default=None
191+
Override the verbose_level for the serialized output. See to_dict() for details.
189192
190193
kwargs: Any other kwargs you pass will be passed on to Python's json.dumps()
191194
@@ -208,27 +211,35 @@ def to_json(self, default_mapping: Optional[dict]=None, force_use_builtin_json=F
208211
>>> ddiff.to_json(default_mapping=default_mapping)
209212
'{"type_changes": {"root": {"old_type": "A", "new_type": "B", "old_value": "obj A", "new_value": "obj B"}}}'
210213
"""
211-
dic = self.to_dict(view_override=TEXT_VIEW)
214+
dic = self.to_dict(verbose_level=verbose_level)
212215
return json_dumps(
213216
dic,
214217
default_mapping=default_mapping,
215218
force_use_builtin_json=force_use_builtin_json,
216219
**kwargs,
217220
)
218221

219-
def to_dict(self, view_override: Optional[str]=None) -> dict:
222+
def to_dict(self, verbose_level: Optional[int]=None) -> dict:
220223
"""
221-
convert the result to a python dictionary. You can override the view type by passing view_override.
224+
Convert the result to a python dictionary.
222225
223226
**Parameters**
224227
225-
view_override: view type, default=None,
226-
override the view that was used to generate the diff when converting to the dictionary.
227-
The options are the text or tree.
228+
verbose_level: int, default=None
229+
Override the verbose_level for the serialized output.
230+
When None, the behavior depends on the original view:
231+
- If the original view is 'text', the verbose_level from DeepDiff initialization is used.
232+
- If the original view is 'tree', verbose_level=2 is used to provide the most detailed output.
233+
Valid values are 0, 1, or 2.
228234
"""
229-
230-
view = view_override if view_override else self.view # type: ignore
231-
return dict(self._get_view_results(view)) # type: ignore
235+
if verbose_level is not None and verbose_level not in {0, 1, 2}:
236+
raise ValueError('verbose_level should be 0, 1, or 2.')
237+
if verbose_level is None:
238+
if self.view == TREE_VIEW: # type: ignore
239+
verbose_level = 2
240+
else:
241+
verbose_level = self.verbose_level # type: ignore
242+
return dict(self._get_view_results(TEXT_VIEW, verbose_level=verbose_level)) # type: ignore
232243

233244
def _to_delta_dict(
234245
self,

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Changelog
55

66
DeepDiff Changelog
77

8+
- v8-7-0
9+
- Support for python 3.14
10+
- ``to_dict()`` and ``to_json()`` now accept a ``verbose_level`` parameter and always return a usable text-view dict. When the original view is ``'tree'``, they default to ``verbose_level=2`` for full detail. The old ``view_override`` parameter is removed.
11+
812
- v8-6-1
913
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
1014

docs/serialization.rst

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ To Dict
1111
-------
1212

1313
In order to convert the DeepDiff object into a normal Python dictionary, use the to_dict() method.
14+
The result is always a text-view dictionary regardless of the original view used to create the DeepDiff object.
15+
16+
**Parameters**
17+
18+
verbose_level: int, default=None
19+
Override the verbose_level for the serialized output.
20+
When None, the behavior depends on the original view:
21+
22+
- If the original view is 'text', the verbose_level from DeepDiff initialization is used.
23+
- If the original view is 'tree', verbose_level=2 is used to provide the most detailed output.
24+
25+
Valid values are 0, 1, or 2.
1426

1527
Example:
1628
>>> t1 = {1: 1, 2: 2, 3: 3, 4: {"a": "hello", "b": [1, 2, 3]}}
@@ -20,15 +32,22 @@ Example:
2032
{'type_changes': {"root[4]['b']": {'old_type': <class 'list'>, 'new_type': <class 'str'>, 'old_value': [1, 2, 3], 'new_value': 'world\n\n\nEnd'}}}
2133

2234

23-
Note that you can override the :ref:`view_label` that was originally used to generate the diff here.
35+
When the original view is 'tree', to_dict() defaults to verbose_level=2 for the most detailed output:
2436

2537
Example:
2638
>>> t1 = {1: 1, 2: 2, 3: 3, 4: {"a": "hello", "b": [1, 2, 3]}}
2739
>>> t2 = {1: 1, 2: 2, 3: 3, 4: {"a": "hello", "b": "world\n\n\nEnd"}}
2840
>>> ddiff = DeepDiff(t1, t2, view='tree')
29-
>>> ddiff.to_dict(view_override='text')
41+
>>> ddiff.to_dict()
3042
{'type_changes': {"root[4]['b']": {'old_type': <class 'list'>, 'new_type': <class 'str'>, 'old_value': [1, 2, 3], 'new_value': 'world\n\n\nEnd'}}}
3143

44+
You can also override the verbose_level:
45+
46+
Example:
47+
>>> ddiff = DeepDiff(t1, t2, view='tree')
48+
>>> ddiff.to_dict(verbose_level=0)
49+
{'type_changes': {"root[4]['b']": {'old_type': <class 'list'>, 'new_type': <class 'str'>}}}
50+
3251
.. _to_json_label:
3352

3453
To Json
@@ -46,6 +65,9 @@ by default DeepDiff converts certain data types. For example Decimals into float
4665
If you have a certain object type that the json serializer can not serialize it, please pass the appropriate type
4766
conversion through this dictionary.
4867

68+
verbose_level: int, default=None
69+
Override the verbose_level for the serialized output. Same behavior as to_dict().
70+
4971
kwargs: Any other kwargs you pass will be passed on to Python's json.dumps()
5072

5173

tests/test_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
class TestCommands:
1212

1313
@pytest.mark.parametrize('name1, name2, expected_in_stdout, expected_exit_code', [
14-
('t1.json', 't2.json', """dictionary_item_added": [\n "root[0][\'key3\']""", 0),
14+
('t1.json', 't2.json', """dictionary_item_added": {\n "root[0]['key3']": "value3\"""", 0),
1515
('t1_corrupt.json', 't2.json', "Error when loading t1:", 1),
1616
('t1.json', 't2_json.csv', '"old_value": "value2"', 0),
1717
('t2_json.csv', 't1.json', '"old_value": "value3"', 0),

tests/test_serialization.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,74 @@ def test_to_dict_at_different_verbose_level(self, verbose_level, expected):
123123
ddiff = DeepDiff(t1, t2, verbose_level=verbose_level)
124124
assert expected == ddiff.to_dict()
125125

126+
def test_to_dict_tree_view_defaults_to_verbose_level_2(self):
127+
t1 = ['a', {1: 1, 3: 4}]
128+
t2 = [10, {1: 2, 5: 6}, 'd']
129+
ddiff = DeepDiff(t1, t2, view='tree')
130+
result = ddiff.to_dict()
131+
# tree view defaults to verbose_level=2: added/removed are dicts, not sets
132+
expected = {
133+
"type_changes": {"root[0]": {"old_type": str, "new_type": int, "old_value": "a", "new_value": 10}},
134+
"dictionary_item_added": {"root[1][5]": 6},
135+
"dictionary_item_removed": {"root[1][3]": 4},
136+
"values_changed": {"root[1][1]": {"new_value": 2, "old_value": 1}},
137+
"iterable_item_added": {"root[2]": "d"},
138+
}
139+
assert expected == result
140+
141+
@pytest.mark.parametrize('verbose_level, expected', [
142+
(0, {"type_changes": {"root[0]": {"old_type": str, "new_type": int}}, "dictionary_item_added": ["root[1][5]"], "dictionary_item_removed": ["root[1][3]"], "iterable_item_added": {"root[2]": "d"}}),
143+
(1, {"type_changes": {"root[0]": {"old_type": str, "new_type": int, "old_value": "a", "new_value": 10}}, "dictionary_item_added": ["root[1][5]"], "dictionary_item_removed": ["root[1][3]"], "values_changed": {"root[1][1]": {"new_value": 2, "old_value": 1}}, "iterable_item_added": {"root[2]": "d"}}),
144+
])
145+
def test_to_dict_tree_view_with_verbose_level_override(self, verbose_level, expected):
146+
t1 = ['a', {1: 1, 3: 4}]
147+
t2 = [10, {1: 2, 5: 6}, 'd']
148+
ddiff = DeepDiff(t1, t2, view='tree')
149+
result = ddiff.to_dict(verbose_level=verbose_level)
150+
assert expected == result
151+
152+
def test_to_dict_text_view_preserves_original_verbose_level(self):
153+
t1 = ['a', {1: 1, 3: 4}]
154+
t2 = [10, {1: 2, 5: 6}, 'd']
155+
# verbose_level=2 at init, text view
156+
ddiff = DeepDiff(t1, t2, verbose_level=2)
157+
result = ddiff.to_dict()
158+
# Should preserve verbose_level=2 from init
159+
assert isinstance(result.get("dictionary_item_added"), dict)
160+
assert result["dictionary_item_added"] == {"root[1][5]": 6}
161+
162+
def test_to_dict_text_view_with_verbose_level_override(self):
163+
t1 = ['a', {1: 1, 3: 4}]
164+
t2 = [10, {1: 2, 5: 6}, 'd']
165+
# verbose_level=2 at init, but override to 1 in to_dict
166+
ddiff = DeepDiff(t1, t2, verbose_level=2)
167+
result = ddiff.to_dict(verbose_level=1)
168+
# verbose_level=1: dictionary_item_added is not a dict (it's a SetOrdered)
169+
assert not isinstance(result.get("dictionary_item_added"), dict)
170+
171+
def test_to_dict_invalid_verbose_level(self):
172+
t1 = [1]
173+
t2 = [2]
174+
ddiff = DeepDiff(t1, t2)
175+
with pytest.raises(ValueError):
176+
ddiff.to_dict(verbose_level=3)
177+
178+
def test_to_json_with_verbose_level(self):
179+
t1 = ['a', {1: 1, 3: 4}]
180+
t2 = [10, {1: 2, 5: 6}, 'd']
181+
ddiff = DeepDiff(t1, t2, view='tree')
182+
result = json.loads(ddiff.to_json())
183+
# tree view defaults to verbose_level=2 in to_json too
184+
assert "root[1][5]" in result.get("dictionary_item_added", {})
185+
186+
def test_to_json_with_verbose_level_override(self):
187+
t1 = ['a', {1: 1, 3: 4}]
188+
t2 = [10, {1: 2, 5: 6}, 'd']
189+
ddiff = DeepDiff(t1, t2, view='tree')
190+
result = json.loads(ddiff.to_json(verbose_level=1))
191+
# verbose_level=1: dictionary_item_added is a list
192+
assert isinstance(result.get("dictionary_item_added"), list)
193+
126194
def test_serialize_pydantic_model(self):
127195
obj = SampleSchema(
128196
works=True,

0 commit comments

Comments
 (0)