Python Code for Dictionary Dot Accessible & Nested Key Handling
In Python, dictionaries are a versatile data structure that allows us to store key-value pairs. However, they might not always be convenient to use, especially when we're dealing with nested structures and we'd prefer to access them using dot notation, much like we would with an object's attributes. So, today, we're going to create a Python class that allows for dot-accessible dictionary keys and automatic nested key handling.
Class Creation
The core of our solution is to create a Python class that inherits from the dict class, and override certain magic methods to change the behavior of our dictionary. We'll call our class DotAccessibleDict.
dot_dict.py
import json
class DotAccessibleDict(dict):
"""
A dictionary that allows dot notation to access keys.
also it allows to access and generate nested structure.
ex) data = DotAccessibleDict()
data.new_key.generate_test.nested_key = 1
functions:
get("key", default) : returns the value for key if key is in the dictionary, else default
dict() : returns a dict object
json() : returns a json string
"""
def __init__(self, *args, **kwargs):
super(DotAccessibleDict, self).__init__(*args, **kwargs)
for arg in args:
if isinstance(arg, dict):
for k, v in arg.items():
self[k] = v
if kwargs:
for k, v in kwargs.items():
self[k] = v
def __getattr__(self, attr):
if attr in self:
return self[attr]
else:
self[attr] = type(self)()
return self[attr]
def __setattr__(self, key, value):
if isinstance(value, dict):
self[key] = type(self)(value)
else:
self[key] = value
def __setitem__(self, key, value):
if isinstance(value, dict):
super(DotAccessibleDict, self).__setitem__(key, type(self)(value))
else:
super(DotAccessibleDict, self).__setitem__(key, value)
def __delattr__(self, attr):
try:
del self[attr]
except KeyError:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'")
def get(self, key, default=None):
if key in self:
return super().get(key, default)
else:
return default
def dict(self):
result = {}
for key, value in self.items():
if isinstance(value, DotAccessibleDict):
result[key] = value.dict()
else:
result[key] = value
return result
def json(self):
return json.dumps(self.dict(), ensure_ascii=False)
Explanation
This class works by creating a new instance of itself every time a non-existent attribute is accessed. This means that we can keep chaining attribute accesses and create deeply nested structures with ease.
Moreover, when setting a value, if the value is a dictionary, we convert it to an instance of DotAccessibleDict. This ensures that nested dictionaries also support dot-accessible keys.
We also override the __setitem__ method to ensure that nested dictionaries are converted to DotAccessibleDict instances when set using index notation.
Finally, we implement dict() and json() methods that return a regular dictionary and a JSON string representation of our dictionary, respectively. The dict() method works by recursively converting all DotAccessibleDict instances to regular dictionaries.
Example
test.py
from dot_dict import DotAccessibleDict
## test code
if __name__ == "__main__":
data = DotAccessibleDict()
data.newkey.first = 1
print(data) # {'newkey': {'first': 1}}
print(data.newkey.first) # 1
data.newkey.second = {"issue":{"message":"blahblah", "code":9}}
print(data) # {'newkey': {'first': 1, 'second': {'issue': {'message': 'blahblah', 'code': 9}}}}
print(data.newkey.second.issue.message) # 'blahblah'
print(f"to dict --> {data.dict()}")
print(f"to json --> {data.json()}")