如何将树类对象结构序列化为JSON文件格式?

10 投票
2 回答
11639 浏览
提问于 2025-04-18 06:13

下面这段代码示例中,我想知道如何用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 个回答

1

这里有一个替代的答案,基本上是我之前在这个问题上的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'>
10

因为你在处理树形结构,所以使用嵌套字典是很自然的选择。下面这段代码创建了一个字典的子类,并把它自己作为实例的底层 __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": []
            }
          ]
        }
      ]
    }
  ]
}

撰写回答