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

2024-05-14 22:08:41 发布

您现在位置:Python中文网/ 问答频道 /正文

给定下面的代码示例,如何使用Python3用JSON序列化这些类实例?

class TreeNode():
    def __init__(self, name):
        self.name = name
        self.children = []

当我尝试执行json.dumps时,会出现以下错误:

TypeError: <TreeNode object at 0x7f6sf4276f60> is not JSON serializable

然后我发现,如果我将默认值设置为json.dumps以返回__dict__,我可以很好地序列化它,但执行json.loads就成了一个问题。

我可以找到很多带有基本字符串的自定义编码器/解码器示例,但是没有一个示例有列表,在本例中是self.children。子节点列表将包含子节点及其子节点其他节点。我需要一种方法来得到所有的一切。


Tags: 实例代码nameselfjson示例列表节点
2条回答

因为您处理的是树结构,所以使用嵌套字典是很自然的。下面的代码片段创建了一个dict的子类,并将自己用作实例的底层__dict__,这是我在许多不同上下文中遇到的一个有趣而有用的技巧:

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 (詹姆斯·罗伯特的博客)
AttrDict (ActiveState配方)
Dictionary with attribute-style access (ActiveState配方)

……事实上,我常常认为这是一个(不太出名的)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": []
            }
          ]
        }
      ]
    }
  ]
}

这里有一个替代的答案,基本上是Python 3版本的my answer对问题Making object JSON serializable with regular encoder的回答,这个问题可以pickle常规的json编码器尚未处理的任何Python对象。

有一些不同。其一,它不会对json模块进行monkey修补,因为这不是解决方案的基本部分。另一个是,尽管这次TreeNode不是从dict类派生的,但它的功能基本上是相同的。这样做是为了防止stockJSONEncoder对其进行编码,并导致使用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'>

相关问题 更多 >

    热门问题