Since you're dealing with a tree structure, it's natural to use nested dictionaries. The snippet of code below creates a subclass of dict
and uses itself as the underlying __dict__
of the instance — which is an interesting and useful trick I've run across in many different contexts:
Is it preferable to return an anonymous class or an object to use as a 'struct'? (stackoverflow)
How to use a dot “.” to access members of dictionary? (stackoverflow)
jsobject.py
(PyDoc.net)
Making Python Objects that act like Javascript Objects
(James Robert's blog)
AttrDict
(ActiveState recipe)
Dictionary with attribute-style access
(ActiveState recipe)
…so often in fact, that I consider it to be a (less well-known) Python idiom.
class TreeNode(dict):
def __init__(self, name, children=None):
super().__init__()
self.__dict__ = self
self.name = name
self.children = list(children) if children is not None else []
This solves half the serialization battle, but when the data produced is read back in with json.loads()
it will be a regular dictionary object, not an instance of TreeNode
. This is because JSONEncoder
can encode dictionaries (and subclasses of them) itself.
One way to address that is add an alternative constructor method to the TreeNode
class that can be called to reconstruct the data structure from the nested dictionary that json.loads()
returns.
Here's what I mean:
...
@staticmethod
def from_dict(dict_):
""" Recursively (re)construct TreeNode-based tree from dictionary. """
node = TreeNode(dict_['name'], dict_['children'])
# node.children = [TreeNode.from_dict(child) for child in node.children]
node.children = list(map(TreeNode.from_dict, node.children))
return node
if __name__ == '__main__':
import json
tree = TreeNode('Parent')
tree.children.append(TreeNode('Child 1'))
child2 = TreeNode('Child 2')
tree.children.append(child2)
child2.children.append(TreeNode('Grand Kid'))
child2.children[0].children.append(TreeNode('Great Grand Kid'))
json_str = json.dumps(tree, indent=2)
print(json_str)
print()
pyobj = TreeNode.from_dict(json.loads(json_str)) # reconstitute
print('pyobj class: {}'.format(pyobj.__class__.__name__)) # -> TreeNode
print(json.dumps(pyobj, indent=2))
Output:
{
"name": "Parent",
"children": [
{
"name": "Child 1",
"children": []
},
{
"name": "Child 2",
"children": [
{
"name": "Grand Kid",
"children": [
{
"name": "Great Grand Kid",
"children": []
}
]
}
]
}
]
}
pyobj class: TreeNode
{
"name": "Parent",
"children": [
{
"name": "Child 1",
"children": []
},
{
"name": "Child 2",
"children": [
{
"name": "Grand Kid",
"children": [
{
"name": "Great Grand Kid",
"children": []
}
]
}
]
}
]
}