Last active
August 15, 2023 11:54
-
-
Save markhu/fbbab71359af00e527d0 to your computer and use it in GitHub Desktop.
edict: load JSON into Python object but access with .dot notation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class edict(dict): # Similar to bunch, but less, and JSON-centric | |
# based on class dotdict(dict): # from http://stackoverflow.com/questions/224026/dot-notation-for-dictionary-keys | |
__setattr__= dict.__setitem__ # TBD: support assignment of nested dicts by overriding this? | |
__delattr__= dict.__delitem__ | |
def __init__(self, data): | |
if type(data) in ( unicode, str ): | |
data = json.loads( data) | |
for name, value in data.iteritems(): | |
setattr(self, name, self._wrap(value)) | |
def __getattr__(self, attr): | |
return self.get(attr, None) | |
def _wrap(self, value): # from class Struct by XEye '11 http://stackoverflow.com/questions/1305532/convert-python-dict-to-object | |
if isinstance(value, (tuple, list, set, frozenset)): | |
return type(value)([self._wrap(v) for v in value]) # recursion! | |
else: | |
if isinstance(value, dict): | |
return edict(value) # is there a relative way to get class name? | |
else: | |
return value | |
# optional handy member function(s): pretty-printer, ... left as an exercise for the reader... | |
# def subset(self, only_keys=[], exclude_keys=[], as='json', indent=2, separators=(',',':')): | |
# EOF |
Getting an error: TypeError: descriptor 'setitem' for 'dict' objects doesn't apply to 'edict' object
@markhu If I may, I'd like to suggest an improved version that allows the usage of getattr
for nested values that mimics that of a completely flattened json object.
from functools import reduce
import json
class edict(dict):
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
def __init__(self, data):
if isinstance(data, str):
data = json.loads(data)
for name, value in data.items():
setattr(self, name, self._wrap(value))
def __getattr__(self, attr):
def _traverse(obj, attr):
if self._is_indexable(obj):
try:
return obj[int(attr)]
except:
return None
elif isinstance(obj, dict):
return obj.get(attr, None)
else:
return attr
if '.' in attr:
return reduce(_traverse, attr.split('.'), self)
return self.get(attr, None)
def _wrap(self, value):
if self._is_indexable(value):
# (!) recursive (!)
return type(value)([self._wrap(v) for v in value])
elif isinstance(value, dict):
return edict(value)
else:
return value
@staticmethod
def _is_indexable(obj):
return isinstance(obj, (tuple, list, set, frozenset))
test_dict = {
"dimensions": {
"length": "112",
"width": "103",
"height": "42"
},
"meta_data": [
{
"id": 11089769,
"key": "imported_gallery_files",
"value": [
"https://abc.com/wp-content/uploads/2019/09/unnamed-3.jpg",
"https://abc.com/wp-content/uploads/2019/09/unnamed-2.jpg",
"https://abc.com/wp-content/uploads/2019/09/unnamed-4.jpg"
]
}
]
}
dotted_dict = edict(test_dict)
print(dotted_dict.dimensions.length) # => '112'
print(getattr(dotted_dict, 'dimensions.length')) # => '112'
print(dotted_dict.meta_data[0].key) # => 'imported_gallery_files'
print(getattr(dotted_dict, 'meta_data.0.key')) # => 'imported_gallery_files'
print(dotted_dict.meta_data[0].value) # => ['link1','link2','link2']
print(getattr(dotted_dict, 'meta_data.0.value')) # => ['link1','link2','link3']
print(dotted_dict.meta_data[0].value[2]) # => 'link3'
print(getattr(dotted_dict, 'meta_data.0.value.2')) # => 'link3'
๐ ๐ to you @caffeinatedMike
@caffeinatedMike This code snippet looks good, but what if you want to add ability if value is None: return default_value
I am new in python and do not know other solution, my modification:
class JsonOptions(dict):
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __init__(self, payload):
super().__init__()
if isinstance(payload, str):
payload = json.loads(payload)
for name, value in payload.items():
setattr(self, name, self._wrap(value))
def __getattr__(self, attr: str):
return self.attr(attr)
def _wrap(self, value):
if self._is_indexable(value):
# (!) recursive (!)
return type(value)([self._wrap(v) for v in value])
elif isinstance(value, dict):
return JsonOptions(value)
else:
return value
def attr(self, attr: str, default=None):
def _traverse(o, name):
if o is None:
return None
if self._is_indexable(o):
try:
return o[int(name)]
except (IndexError, ValueError):
return None
elif isinstance(o, dict):
return o.get(name, None)
else:
return None
if '.' in attr:
value = reduce(_traverse, attr.split('.'), self)
else:
value = self.get(attr, None)
return default if value is None else value
@staticmethod
def _is_indexable(o):
return isinstance(o, (tuple, list, set, frozenset))
P.S. is there a more nice solution
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample usage: