Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement json converter and representation #139

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ docs/_build/
# PyBuilder
target/


#OSX Files
# OSX Files
.DS_Store

# IDE caches
.idea/
.vscode/

34 changes: 34 additions & 0 deletions addict/addict.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import copy
import json


class Dict(dict):
__json__ = False # override this for JSON representation

def __init__(__self, *args, **kwargs):
object.__setattr__(__self, '__parent', kwargs.pop('__parent', None))
Expand Down Expand Up @@ -157,3 +159,35 @@ def freeze(self, shouldFreeze=True):

def unfreeze(self):
self.freeze(False)

def json(self, force=True):
base = {}
for key, value in self.items():
if isinstance(value, (type(self), dict)):
base[key] = Dict(value).json()
elif isinstance(value, (list, tuple)):
base[key] = list(
(Dict(item).json() if callable(item.json) else Dict(item).json)
if isinstance(item, (type(self), dict)) or hasattr(item, "json")
else item
if isinstance(item, (int, float, bool, str, type(None)))
else str(item) if force else item
for item in value)
elif isinstance(value, (int, float, bool, str, type(None))):
base[key] = value
elif hasattr(value, "json"):
base[key] = value.json() if callable(value.json) else value.json
else:
base[key] = str(value) if force else value
return base

def __repr__(self):
if self.__json__:
cls = type(self)
try:
repr_ = json.dumps(self.json(force=False),
indent=2, ensure_ascii=False)
except Exception: # noqa
return dict.__repr__(self)
return "{0}.{1}\n{2}".format(cls.__module__, cls.__name__, repr_)
return dict.__repr__(self)
112 changes: 108 additions & 4 deletions test_addict.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import json
import collections
import copy
import json
import unittest
import pickle
import sys
import uuid
from inspect import cleandoc
from addict import Dict

# patch auto ordered dict for Python < 3.7
# whenever tests need ordering compatibility
if sys.version_info < (3, 7):
_dict = collections.OrderedDict
else:
_dict = dict


# test whether unittests pass on child classes
class CHILD_CLASS(Dict):
Expand Down Expand Up @@ -267,7 +278,7 @@ def update():
org.update({'a': 2}, {'a': 1})
org = self.dict_class()
self.assertRaises(TypeError, update)

def test_ior_operator(self):
old = self.dict_class()
old.child.a = 'a'
Expand Down Expand Up @@ -337,7 +348,7 @@ def test_or_operator_with_lists(self):
org = org | someother
self.assertDictEqual(org, correct)
self.assertIsInstance(org.b[0], dict)

def test_ror_operator(self):
org = dict()
org['a'] = [1, 2, {'a': 'superman'}]
Expand All @@ -352,7 +363,7 @@ def test_ror_operator(self):
self.assertDictEqual(org, correct)
self.assertIsInstance(org, Dict)
self.assertIsInstance(org.b[0], dict)

def test_or_operator_type_error(self):
old = self.dict_class()
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -572,6 +583,99 @@ def test_top_freeze_disallows_new_key_addition(self):
d.newKey = TEST_VAL
self.assertEqual(d.newKey, TEST_VAL)

def test_json_conversion(self):
"Test that d.json() returns a JSON compatible nested dict."
class JsonClassMethod(object): # not JSON serializable
def json(self): # but implements json, so will use it to serialize
return {"json": "class"}

# same thing, but using property instead of method
class JsonClassProperty(object):
@property
def json(self):
return {"json": "attr"}

_uuid = uuid.uuid4() # not serializable directly, but string repr
d = self.dict_class({"dict": {"other": "value", "key": {"ok": "no"}},
"meth": {"cls": JsonClassMethod()},
"prop": JsonClassProperty(),
"sub": Dict({"ok": 1}), # nested
"uuid": _uuid,
"tuple": (1, 2, 3, False), # to JSON array
"list": [
"1", 2, 3.3, None, {"dict": {"ok": True}}
]})
expect = {
"dict": {"other": "value", "key": {"ok": "no"}},
"meth": {"cls": {"json": "class"}},
"prop": {"json": "attr"},
"sub": {"ok": 1},
"uuid": str(_uuid),
"tuple": [1, 2, 3, False],
"list": ["1", 2, 3.3, None, {"dict": {"ok": True}}]
}
self.assertEqual(d.json(), expect)

def test_json_repr(self):
"Test that JSON representation is used when requested with __json__"
class JsonRepr(self.dict_class):
# must enforce JSON repr method, so that existing repr don't break
# (backward compatibility)
__json__ = True

data = _dict([
("top", _dict([("x", 0), ("y", -1)])),
("dict", self.dict_class(_dict([("a", 1), ("b", None)]))),
("list", [Dict(_dict([("c", 3), ("d", "ok")])), 4.5])
])
d = JsonRepr(data)
_repr = cleandoc("""
test_addict.JsonRepr
{
"top": {
"x": 0,
"y": -1
},
"dict": {
"a": 1,
"b": null
},
"list": [
{
"c": 3,
"d": "ok"
},
4.5
]
}
""")
if sys.version_info < (3, ):
_repr = _repr.replace(",\n", ", \n")
self.assertEqual(repr(d), _repr)

# check that normal repr still works as usual (__json__ = False)
# don't need OrderedDict for Python 2.7 because handled the same way
data = {"dict": Dict({"a": 1, "b": None}),
"list": [Dict({"c": 3, "d": "ok"}), 4.5]}
d = self.dict_class(data)
self.assertEqual(repr(d), str(data))

def test_json_repr_not_serializable(self):
"Test that failing JSON representation falls back to the dict repr"
class JsonRepr(self.dict_class):
__json__ = True

class NotSerializable(object):
pass

data = _dict({"x": 1, "y": NotSerializable()})
json_d = JsonRepr(data)
dict_d = self.dict_class(data)
json_r = repr(json_d) # shouldn't raise
dict_r = repr(dict_d) # normal repr
self.assertEqual(json_r, dict_r)


class DictTests(unittest.TestCase, AbstractTestsClass):
dict_class = Dict

Expand Down