Skip to content

Commit 419d2bd

Browse files
committed
Another fairly extensive overhaul to address lists as roots, embedded objects and other "edge" cases
This will be released as a dev release so we don't blow stuff up
1 parent 4514ec4 commit 419d2bd

File tree

10 files changed

+21484
-34
lines changed

10 files changed

+21484
-34
lines changed

ChangeLog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGES
22
=======
33

4+
* Add a build badge
5+
* Fix issue #10
6+
7+
v2.0.1
8+
------
9+
410
* Ignore Pipfile.lock
511
* Fairly extensive refactoring
612

jsonasobj/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from jsonasobj.jsonobj import JsonObj, as_dict, as_list, as_json, as_json_obj, get, items, loads, load, setdefault
1+
from jsonasobj._jsonobj import JsonObj, as_dict, as_list, as_json, as_json_obj, get, items, loads, load, setdefault, \
2+
keys, items
23
from jsonasobj.extendednamespace import ExtendedNamespace
34

45
__all__ = ['JsonObj', 'ExtendedNamespace', 'as_dict', 'as_list', 'as_json', 'as_json_obj', 'get', 'items',
5-
'load', 'loads', 'setdefault']
6+
'load', 'loads', 'setdefault', 'keys']
Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Union, List, Dict, Tuple, Optional, Callable, Any, Iterator
33
from hbreader import hbread
44

5-
from .extendednamespace import ExtendedNamespace
5+
from jsonasobj.extendednamespace import ExtendedNamespace
66

77
# Possible types in the JsonObj representation
88
JsonObjTypes = Union["JsonObj", List["JsonObjTypes"], str, bool, int, float, None]
@@ -19,10 +19,29 @@ class JsonObj(ExtendedNamespace):
1919
identifier is represented as a first-class member of the objects. JSON identifiers that begin with "_" are
2020
disallowed in this implementation.
2121
"""
22+
def __new__(cls, list_or_dict: Optional[Union[List, Dict]] = None, *,
23+
_if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None, **kwargs):
24+
""" Construct a JsonObj from set of keyword/value pairs
25+
26+
:param list_or_dict: A list or dictionary that can be used to construct the object
27+
:param _if_missing: Function to call if attempt is made to access an undefined value. Function takes JsonObj
28+
instance and parameter as input and returns a tuple -- handled (y or n) and result. If handled is 'n' inline
29+
processing proceeds.
30+
:param kwargs: A dictionary as an alternative constructor.
31+
"""
32+
# This makes JsonObj idempotent
33+
if isinstance(list_or_dict, JsonObj):
34+
if not kwargs and (not _if_missing or _if_missing == list_or_dict._if_missing):
35+
return list_or_dict
36+
else:
37+
obj = super(ExtendedNamespace, cls).__new__(cls)
38+
obj.__init__(as_json_obj(list_or_dict), _if_missing=_if_missing, **kwargs)
39+
else:
40+
obj = super(ExtendedNamespace, cls).__new__(cls)
41+
return obj
2242

2343
def __init__(self, list_or_dict: Optional[Union[List, Dict]] = None, *,
24-
_if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None,
25-
**kwargs) -> None:
44+
_if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None, **kwargs):
2645
""" Construct a JsonObj from set of keyword/value pairs
2746
2847
:param list_or_dict: A list or dictionary that can be used to construct the object
@@ -31,12 +50,17 @@ def __init__(self, list_or_dict: Optional[Union[List, Dict]] = None, *,
3150
processing proceeds.
3251
:param kwargs: A dictionary as an alternative constructor.
3352
"""
53+
if isinstance(list_or_dict, JsonObj):
54+
return
55+
3456
if _if_missing and _if_missing != self._if_missing:
3557
self._if_missing = _if_missing
3658
if list_or_dict is not None:
37-
if len(kwargs):
59+
if kwargs:
3860
raise TypeError("Constructor can't have both a single item and a dict")
39-
if isinstance(list_or_dict, (dict, JsonObj)):
61+
if isinstance(list_or_dict, JsonObj):
62+
pass
63+
elif isinstance(list_or_dict, dict):
4064
self._init_from_dict(list_or_dict)
4165
elif isinstance(list_or_dict, list):
4266
ExtendedNamespace.__init__(self,
@@ -53,10 +77,15 @@ def _if_missing(obj: "JsonObj", item: str) -> Tuple[bool, Any]:
5377

5478
def _init_from_dict(self, d: Union[dict, "JsonObj"]) -> None:
5579
""" Construct a JsonObj from a dictionary or another JsonObj """
56-
if not isinstance(d, dict):
57-
d = d._as_dict
58-
ExtendedNamespace.__init__(self, _if_missing=self._if_missing, **{k: JsonObj(**v) if isinstance(v, dict) else v
59-
for k, v in d.items()})
80+
if not isinstance(d, JsonObj):
81+
ExtendedNamespace.__init__(self, _if_missing=self._if_missing,
82+
**{k: JsonObj(v) if isinstance(v, dict) else v for k, v in d.items()})
83+
84+
def _hide_list(self):
85+
return self._root if '_root' in self else self
86+
87+
def __str__(self) -> str:
88+
return str(self._root) if '_root' in self else super().__str__()
6089

6190
# ===================================================
6291
# JSON Serializer method
@@ -86,13 +115,13 @@ def _setdefault(self, k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes:
86115

87116
def _keys(self) -> List[str]:
88117
""" Return all non-hidden keys """
89-
for k in self.__dict__.keys():
118+
for k in self._hide_list().__dict__.keys():
90119
if k not in hide:
91120
yield k
92121

93122
def _items(self) -> List[Tuple[str, JsonObjTypes]]:
94123
""" Return all non-hidden items """
95-
for k, v in self.__dict__.items():
124+
for k, v in self._hide_list().__dict__.items():
96125
if k not in hide:
97126
yield k, v
98127

@@ -172,7 +201,8 @@ def loads(s: str, **kwargs) -> JsonObj:
172201
"""
173202
if isinstance(s, (bytes, bytearray)):
174203
s = s.decode(json.detect_encoding(s), 'surrogatepass')
175-
return json.loads(s, object_hook=lambda pairs: JsonObj(**pairs), **kwargs)
204+
205+
return JsonObj(json.loads(s, object_hook=lambda pairs: JsonObj(pairs), **kwargs))
176206

177207

178208
def load(source, **kwargs) -> JsonObj:
@@ -191,18 +221,19 @@ def as_dict(obj: Union[JsonObj, List]) -> Union[List, Dict[str, JsonTypes]]:
191221
:param obj: pseudo 'self'
192222
:return: dictionary that cooresponds to the json object
193223
"""
194-
return [e._as_dict if isinstance(e, JsonObj) else e for e in obj] if isinstance(obj, list) else obj._as_dict
224+
return [e._as_dict if isinstance(e, JsonObj) else e for e in obj] if isinstance(obj, list) else\
225+
obj._as_dict if isinstance(obj, JsonObj) else obj
195226

196227

197228
def as_list(obj: Union[JsonObj, List]) -> List[JsonTypes]:
198229
""" Return a json array as a list
199230
200231
:param obj: pseudo 'self'
201232
"""
202-
return obj._as_list
233+
return obj._as_list if isinstance(obj, JsonObj) else obj
203234

204235

205-
def as_json(obj: Union[JsonObj, List], indent: Optional[str] = ' ',
236+
def as_json(obj: Union[Dict, JsonObj, List], indent: Optional[str] = ' ',
206237
filtr: Callable[[dict], dict] = None, **kwargs) -> str:
207238
""" Convert obj to json string representation.
208239
@@ -216,35 +247,37 @@ def as_json(obj: Union[JsonObj, List], indent: Optional[str] = ' ',
216247
obj = obj._root
217248
return obj._as_json_dumps(indent, filtr=filtr, **kwargs) if isinstance(obj, JsonObj) else \
218249
json.dumps(obj, default=lambda o: JsonObj._default(o, filtr) if filtr else JsonObj._default(o),
219-
indent=indent, **kwargs)
250+
indent=indent, **kwargs)
220251

221252

222-
def as_json_obj(obj: Union[JsonObj, List]) -> JsonTypes:
253+
def as_json_obj(obj: Union[Dict, JsonObj, List]) -> JsonTypes:
223254
""" Return obj as pure python json (vs. JsonObj)
224255
:param obj: pseudo 'self'
225256
:return: Pure python json image
226257
"""
227-
return [e._as_json_obj() if isinstance(e, JsonObj) else e for e in obj] \
228-
if isinstance(obj, list) else obj._as_json_obj()
258+
if isinstance(obj, JsonObj):
259+
obj = obj._hide_list()
260+
return [as_json_obj(e) if isinstance(e, JsonObj) else e for e in obj] if isinstance(obj, list) else\
261+
obj._as_json_obj() if isinstance(obj, JsonObj) else obj
229262

230263

231-
def get(obj: JsonObj, item: str, default: JsonObjTypes = None) -> JsonObjTypes:
264+
def get(obj: Union[Dict, JsonObj], item: str, default: JsonObjTypes = None) -> JsonObjTypes:
232265
""" Dictionary get routine """
233-
return obj._get(item, default)
266+
return obj._get(item, default) if isinstance(obj, JsonObj) else obj.get(item, default)
234267

235268

236-
def setdefault(obj: JsonObj, k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes:
237-
""" Dictionary setdefault reoutine """
238-
return obj._setdefault(k, value)
269+
def setdefault(obj: Union[Dict, JsonObj], k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes:
270+
""" Dictionary setdefault routine """
271+
return obj._setdefault(k, value) if isinstance(obj, JsonObj) else obj.setdefault(k, value)
239272

240273

241-
def keys(obj: JsonObj) -> Iterator[str]:
274+
def keys(obj: Union[Dict, JsonObj]) -> Iterator[str]:
242275
""" same as dict keys() without polluting the namespace """
243-
return obj._keys()
276+
return obj._keys() if isinstance(obj, JsonObj) else obj.keys()
244277

245278

246-
def items(obj: JsonObj) -> Iterator[Tuple[str, JsonObjTypes]]:
279+
def items(obj: Union[Dict, JsonObj]) -> Iterator[Tuple[str, JsonObjTypes]]:
247280
""" Same as dict items() except that the values are JsonObjs instead of vanilla dictionaries
248281
:return:
249282
"""
250-
return obj._items()
283+
return obj._items() if isinstance(obj, JsonObj) else obj.items()

0 commit comments

Comments
 (0)