【问题标题】:How to serialize a tree class object structure into JSON file format?如何将树类对象结构序列化为 json 文件格式?
【发布时间】:2014-06-29 00:49:53
【问题描述】:

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

【问题讨论】:

    标签: python json serialization python-3.x


    【解决方案1】:

    由于您正在处理树结构,因此使用嵌套字典是很自然的。下面代码的 sn-p 创建了一个 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": []
                }
              ]
            }
          ]
        }
      ]
    }
    

    【讨论】:

    • 整洁,非常感谢。现在只针对 json.loads。当我们重构回 Treenode 时,最好的方法是什么?
    • Biff:您必须使用类似于我在回答中添加的替代构造函数方法来回应您的评论(但没有在此处发表评论明确通知您——我现在就是只为其他读者的利益而做)。
    【解决方案2】:

    这是一个替代答案,它基本上是我的answer 对问题Making object JSON serializable with regular encoder 的 Python 3 版本,它腌制常规 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'>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-15
      • 1970-01-01
      • 2020-07-19
      相关资源
      最近更新 更多