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

时间:2014-05-11 17:34:58

标签: python json serialization python-3.x

鉴于下面的代码示例,如何使用Python 3使用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。子列表将保存子节点及其子节点的其他节点。我需要一种方法来获得所有这些。

2 个答案:

答案 0 :(得分:6)

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

Is it preferable to return an anonymous class or an object to use as a 'struct'? (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 = [] if not children else children

这解决了序列化争斗的一半,但是当用json.loads()读回产生的数据时,它将是常规字典对象,而不是TreeNode的实例。这是因为JSONEncoder可以编码字典(及其子类)本身。

解决这个问题的一种方法是向TreeNode类添加一个替代构造函数方法,可以调用该方法从json.loads()返回的嵌套字典重构数据结构。

这就是我的意思:

    @staticmethod
    def from_dict(dict_):
        """ Recursively (re)construct TreeNode-based tree from dictionary. """
        root = TreeNode(dict_['name'], dict_['children'])
        root.children = list(map(TreeNode.from_dict, root.children))
        return root

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, sort_keys=True, 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, sort_keys=True, indent=2))

输出:

{
  "children": [
    {
      "children": [],
      "name": "Child 1"
    },
    {
      "children": [
        {
          "children": [
            {
              "children": [],
              "name": "Great Grand Kid"
            }
          ],
          "name": "Grand Kid"
        }
      ],
      "name": "Child 2"
    }
  ],
  "name": "Parent"
}

pyobj class: TreeNode
{
  "children": [
      ... same as before ...
  ],
  "name": "Parent"
}

答案 1 :(得分:1)

这里有一个替代答案,它基本上是answer的问题Making object JSON serializable with regular encoder的Python 3版本,用于挑选普通json编码器不支持的任何Python对象# 39;已经处理好了。

存在一些差异。一个是它没有修补json模块,因为它不是解决方案的重要组成部分。另一个是虽然这次TreeNode不是dict类派生的,但它具有基本相同的功能。这样做是为了防止库存JSONEncoder对其进行编码,并导致_default()子类中的JSONEncoder方法被替代使用。

除此之外,它是一种非常通用的方法,能够处理许多其他类型的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'>