Как сериализовать структуру объекта класса дерева в формат файла 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'>