如何将树类对象结构序列化为JSON文件格式?
下面这段代码示例中,我想知道如何用Python 3把这些类的实例转换成JSON格式。
class TreeNode():
def __init__(self, name):
self.name = name
self.children = []
当我尝试使用 json.dumps
时,出现了以下错误:
TypeError: <TreeNode object at 0x7f6sf4276f60> 不能被转换为JSON格式
我发现如果我把 json.dumps
的默认设置为返回 __dict__
,就可以顺利转换,但这样在使用 json.loads
时又会遇到问题。
我找到了很多关于基本字符串的自定义编码器和解码器的例子,但没有找到涉及列表的例子,比如这里的 self.children。这个 children 列表会包含子节点和它们的子节点。我需要找到一种方法来获取所有这些内容。
2 个回答
这里有一个替代的答案,基本上是我之前在这个问题上的Python 3版本,问题是如何让对象可以用普通编码器转成JSON格式,这个方法可以处理任何普通的json
编码器无法处理的Python对象。
有几个不同之处。一个是它没有修改json
模块,因为这不是解决问题的关键部分。另一个是虽然这次TreeNode
类并不是从dict
类派生的,但它的功能基本上是一样的。这样做是为了防止默认的JSONEncoder
对它进行编码,而是使用JSONEncoder
子类中的_default()
方法。
除此之外,这是一种非常通用的方法,可以处理许多其他类型的Python对象,包括用户自定义的类,而无需修改。
import base64
from collections import MutableMapping
import json
import pickle
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
return {'_python_object':
base64.b64encode(pickle.dumps(obj)).decode('utf-8') }
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(base64.b64decode(dct['_python_object']))
return dct
# based on AttrDict -- https://code.activestate.com/recipes/576972-attrdict
class TreeNode(MutableMapping):
""" dict-like object whose contents can be accessed as attributes. """
def __init__(self, name, children=None):
self.name = name
self.children = list(children) if children is not None else []
def __getitem__(self, key):
return self.__getattribute__(key)
def __setitem__(self, key, val):
self.__setattr__(key, val)
def __delitem__(self, key):
self.__delattr__(key)
def __iter__(self):
return iter(self.__dict__)
def __len__(self):
return len(self.__dict__)
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, cls=PythonObjectEncoder, indent=4)
print('json_str:', json_str)
pyobj = json.loads(json_str, object_hook=as_python_object)
print(type(pyobj))
输出:
json_str: {
"_python_object": "gANjX19tYWluX18KVHJlZU5vZGUKcQApgXEBfXECKFgIAAAAY2hp"
"bGRyZW5xA11xBChoACmBcQV9cQYoaANdcQdYBAAAAG5hbWVxCFgH"
"AAAAQ2hpbGQgMXEJdWJoACmBcQp9cQsoaANdcQxoACmBcQ19cQ4o"
"aANdcQ9oACmBcRB9cREoaANdcRJoCFgPAAAAR3JlYXQgR3JhbmQg"
"S2lkcRN1YmFoCFgJAAAAR3JhbmQgS2lkcRR1YmFoCFgHAAAAQ2hp"
"bGQgMnEVdWJlaAhYBgAAAFBhcmVudHEWdWIu"
}
<class '__main__.TreeNode'>
因为你在处理树形结构,所以使用嵌套字典是很自然的选择。下面这段代码创建了一个字典的子类,并把它自己作为实例的底层 __dict__
,这是一种我在很多不同场合见过的有趣且实用的技巧:
返回一个匿名类还是一个对象作为“结构体”更好? (stackoverflow)
如何使用点“.”来访问字典的成员? (stackoverflow)
jsobject.py
(PyDoc.net)
制作像Javascript对象一样的Python对象
(James Robert's blog)
AttrDict
(ActiveState recipe)
具有属性风格访问的字典
(ActiveState recipe)
实际上,这种用法非常普遍,以至于我认为它是一个(不太为人所知的)Python习惯用法。
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 []
这解决了序列化的一半问题,但当用 json.loads()
读取生成的数据时,它会变成一个普通的字典对象,而不是 TreeNode
的实例。这是因为 JSONEncoder
可以自己编码字典(及其子类)。
解决这个问题的一种方法是给 TreeNode
类添加一个替代构造方法,这样可以从 json.loads()
返回的嵌套字典中重建数据结构。
这就是我想说的:
...
@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))
输出:
{
"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": []
}
]
}
]
}
]
}