Как сериализовать структуру объекта класса дерева в формат файла json?

учитывая приведенный ниже пример кода, как я могу сериализовать эти экземпляры класса с помощью JSON с помощью Python 3?

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 становится проблемой.

Я могу найти много пользовательских примеров кодировщика / декодера с базовыми строками, но ни одного, где есть список, в данном случае себя.дети. Список дочерних узлов будет содержать дочерние узлы, а их дочерние узлы-другие узлы. Мне нужен способ получить все это.

2 ответов


поскольку вы имеете дело с древовидной структурой, естественно использовать вложенные словари. Ниже создается подкласс dict и использует себя в качестве базовых __dict__ экземпляра-это интересный и полезный трюк, с которым я столкнулся во многих различных контекстах:

     предпочтительнее ли возвращать анонимный класс или объект для использования в качестве "структуры"? (Stackoverflow)
     jsobject.py (PyDoc.net)
     создание объектов Python, которые действуют как объекты Javascript (Блог Джеймса Роберта)
     AttrDict (ActiveState рецепт)
     словарь с доступом в стиле атрибута (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"
}

вот альтернативный ответ, который в основном является версией Python 3 my ответ на вопрос сделать объект JSON сериализуемым с помощью обычного кодировщика который маринует любые объекты Python, которые обычный кодировщик json еще не обрабатывает.

есть несколько отличий. Во-первых, это не обезьяна-патчjsonмодуль только потому, что это не неотъемлемой частью решения. Другой является то, что хотя theTreeNodeкласс не полученные от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'>