Skip to content

Commit

Permalink
jsonpickle v2.0.0: preserve dictionary identity
Browse files Browse the repository at this point in the history
Reapply the changes to references to dictionaries.

Closes #351
Related-to: #255 #322
Signed-off-by: David Aguilar <davvid@gmail.com>
  • Loading branch information
davvid committed Feb 8, 2021
1 parent 38751c8 commit 6ef91df
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 26 deletions.
8 changes: 8 additions & 0 deletions CHANGES.rst
@@ -1,3 +1,11 @@
v2.0.0
======
* Major release: the serialized JSON format now preserves dictionary
identity, which is a subtle change in the serialized format. (#351)
* Dictionary identity is now preserved. For example, if the same
dictionary appears twice in a list, the reconstituted list
will now contain two references to the same dictionary. (#255) (+332)

v1.5.3
======
* Patch release to avoid the change in behavior from the preservation
Expand Down
46 changes: 20 additions & 26 deletions jsonpickle/pickler.py
Expand Up @@ -271,14 +271,13 @@ def _flatten_impl(self, obj):
if PY2 and isinstance(obj, types.FileType):
return self._flatten_file(obj)

if util.is_bytes(obj):
if type(obj) is bytes:
return self._flatten_bytestring(obj)

if util.is_primitive(obj):
return obj

# Decimal is a primitive when use_decimal is True
if self._use_decimal and isinstance(obj, decimal.Decimal):
if type(obj) in util.PRIMITIVES or (
self._use_decimal and isinstance(obj, decimal.Decimal)
):
return obj
#########################################

Expand Down Expand Up @@ -320,37 +319,32 @@ def _list_recurse(self, obj):
return [self._flatten(v) for v in obj]

def _get_flattener(self, obj):

list_recurse = self._list_recurse

if util.is_list(obj):
if type(obj) in (list, dict):
if self._mkref(obj):
return list_recurse
return (
self._list_recurse if type(obj) is list else self._flatten_dict_obj
)
else:
self._push()
return self._getref

# We handle tuples and sets by encoding them in a "(tuple|set)dict"
if util.is_tuple(obj):
if not self.unpicklable:
return list_recurse
return lambda obj: {tags.TUPLE: [self._flatten(v) for v in obj]}

if util.is_set(obj):
elif type(obj) in (tuple, set):
if not self.unpicklable:
return list_recurse
return lambda obj: {tags.SET: [self._flatten(v) for v in obj]}

if util.is_dictionary(obj):
return self._flatten_dict_obj
return self._list_recurse
return lambda obj: {
tags.TUPLE
if type(obj) is tuple
else tags.SET: [self._flatten(v) for v in obj]
}

elif util.is_object(obj):
return self._ref_obj_instance

if util.is_type(obj):
elif util.is_type(obj):
return _mktyperef

if util.is_object(obj):
return self._ref_obj_instance

if util.is_module_function(obj):
elif util.is_module_function(obj):
return self._flatten_function

# instance methods, lambdas, old style classes...
Expand Down
1 change: 1 addition & 0 deletions jsonpickle/unpickler.py
Expand Up @@ -541,6 +541,7 @@ def _restore_set(self, obj):

def _restore_dict(self, obj):
data = {}
self._mkref(data)

# If we are decoding dicts that can have non-string keys then we
# need to do a two-phase decode where the non-string keys are
Expand Down
9 changes: 9 additions & 0 deletions tests/jsonpickle_test.py
Expand Up @@ -1530,6 +1530,15 @@ def test_cyclical_objects_unpickleable_false_list(self):
self.test_cyclical_objects_unpickleable_false(use_tuple=False)


def test_dict_references_are_preserved():
data = {}
actual = jsonpickle.decode(jsonpickle.encode([data, data]))
assert isinstance(actual, list)
assert isinstance(actual[0], dict)
assert isinstance(actual[1], dict)
assert actual[0] is actual[1]


def test_repeat_objects_are_expanded():
"""Ensure that all objects are present in the json output"""
# When references are disabled we should create expanded copies
Expand Down

0 comments on commit 6ef91df

Please sign in to comment.