22from typing import Union , List , Dict , Tuple , Optional , Callable , Any , Iterator
33from hbreader import hbread
44
5- from .extendednamespace import ExtendedNamespace
5+ from jsonasobj .extendednamespace import ExtendedNamespace
66
77# Possible types in the JsonObj representation
88JsonObjTypes = 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
178208def 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
197228def 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