Кодирование вложенного объекта Python в JSON
Я хочу кодировать объекты в JSON. Но я не могу понять, как сделать вывод без экранирования строки.
import json
class Abc:
def __init__(self):
self.name="abc name"
def toJSON(self):
return json.dumps(self.__dict__, cls=ComplexEncoder)
class Doc:
def __init__(self):
self.abc=Abc()
def toJSON(self):
return json.dumps(self.__dict__, cls=ComplexEncoder)
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Abc) or isinstance(obj, Doc):
return obj.toJSON()
else:
return json.JSONEncoder.default(self, obj)
doc=Doc()
print doc.toJSON()
В результате (dumps возвращает строковое представление, поэтому "экранированы")
{"abc": "{\"name\": \"abc name\"}"}
Я хочу что-то немного другое. Ожидаемый результат
{"abc": {"name": "abc name"}"}
Но я не понимаю, как... Любой намек?
заранее спасибо.
7 ответов
Мой предыдущий пример с другим вложенным объектом и вашими советами:
import json
class Identity:
def __init__(self):
self.name="abc name"
self.first="abc first"
self.addr=Addr()
def reprJSON(self):
return dict(name=self.name, firstname=self.first, address=self.addr)
class Addr:
def __init__(self):
self.street="sesame street"
self.zip="13000"
def reprJSON(self):
return dict(street=self.street, zip=self.zip)
class Doc:
def __init__(self):
self.identity=Identity()
self.data="all data"
def reprJSON(self):
return dict(id=self.identity, data=self.data)
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
if hasattr(obj,'reprJSON'):
return obj.reprJSON()
else:
return json.JSONEncoder.default(self, obj)
doc=Doc()
print "Str representation"
print doc.reprJSON()
print "Full JSON"
print json.dumps(doc.reprJSON(), cls=ComplexEncoder)
print "Partial JSON"
print json.dumps(doc.identity.addr.reprJSON(), cls=ComplexEncoder)
дает ожидаемый результат:
Str representation
{'data': 'all data', 'id': <__main__.Identity instance at 0x1005317e8>}
Full JSON
{"data": "all data", "id": {"name": "abc name", "firstname": "abc first", "address": {"street": "sesame street", "zip": "13000"}}}
Partial JSON
{"street": "sesame street", "zip": "13000"}
Благодарю.
Итак, непосредственная проблема заключается в том, что вы передаете модулю json значение JSON, которое будет закодировано как просто еще одна строка в значении JSON.
Более общая проблема в том, что вы сильно усложняете это.
Рисуя время JSON между Python и JavaScript, я бы подошел к чему-то более близкому к этому:
import json
class Abc:
def __init__(self):
self.name="abc name"
def jsonable(self):
return self.name
class Doc:
def __init__(self):
self.abc=Abc()
def jsonable(self):
return self.__dict__
def ComplexHandler(Obj):
if hasattr(Obj, 'jsonable'):
return Obj.jsonable()
else:
raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj))
doc=Doc()
print json.dumps(doc, default=ComplexHandler)
который получает вас:
~$ python nestjson.py
{"abc": "abc name"}
~$
Это можно сделать чище / разумнее / безопаснее (в частности, просто захватывая __dict__
как правило, не рекомендуется делать вне отладки / устранения неполадок), но с этим нужно разобраться. По сути, все, что вам нужно, - это способ получить json-совместимый объект (будь то простая строка или число, или список, или тип) из каждого "узла" в дереве. Этот объект не должен быть уже сериализованным JSON-объектом, что вы и делали.
Чтобы избежать повторения кода, как в ответе Фреда Лорана, я перегружал __iter__()
Метод следующим образом. Это также позволяет "jsonize" элементам списка, datetime и decimal без дополнительных зависимостей, просто используйте dict().
import datetime
import decimal
class Jsonable(object):
def __iter__(self):
for attr, value in self.__dict__.iteritems():
if isinstance(value, datetime.datetime):
iso = value.isoformat()
yield attr, iso
elif isinstance(value, decimal.Decimal):
yield attr, str(value)
elif(hasattr(value, '__iter__')):
if(hasattr(value, 'pop')):
a = []
for subval in value:
if(hasattr(subval, '__iter__')):
a.append(dict(subval))
else:
a.append(subval)
yield attr, a
else:
yield attr, dict(value)
else:
yield attr, value
class Identity(Jsonable):
def __init__(self):
self.name="abc name"
self.first="abc first"
self.addr=Addr()
class Addr(Jsonable):
def __init__(self):
self.street="sesame street"
self.zip="13000"
class Doc(Jsonable):
def __init__(self):
self.identity=Identity()
self.data="all data"
def main():
doc=Doc()
print "-Dictionary- \n"
print dict(doc)
print "\n-JSON- \n"
print json.dumps(dict(doc), sort_keys=True, indent=4)
if __name__ == '__main__':
main()
Выход:
-Dictionary-
{'data': 'all data', 'identity': {'first': 'abc first', 'addr': {'street': 'sesame street', 'zip': '13000'}, 'name': 'abc name'}}
-JSON-
{
"data": "all data",
"identity": {
"addr": {
"street": "sesame street",
"zip": "13000"
},
"first": "abc first",
"name": "abc name"
}
}
Надеюсь, поможет! Спасибо
Хотя все остальные решения, как я полагаю, работают, я обнаружил, что у них есть много шаблонного кода , когда цель состоит в том, чтобы кодировать только вложенные объекты Python.
В статье я нашел элегантное решение, которое делает именно то, что вы просили, но без стандартного кода. Поскольку вы даже можете получить часть десериализации бесплатно, я сначала покажу вам решение вашего точного вопроса, а затем дам более чистую версию, в которой десериализация также будет работать.
Точное решение вашего вопроса
import json
class Abc(object):
def __init__(self):
self.name = "abc name"
class Doc(object):
def __init__(self):
self.abc = Abc()
doc = Doc()
# Serialization
json_data = json.dumps(doc, default=lambda o: o.__dict__)
print(json_data)
Это выведет именно то, что вы просили:
{"abc": {"name": "abc name"}}
Более элегантное решение для сериализации и десерализации
import json
class Abc(object):
def __init__(self, name: str):
self.name = name
class Doc(object):
def __init__(self, abc):
self.abc = abc
abc = Abc("abc name")
doc = Doc(abc)
# Serialization
json_data = json.dumps(doc, default=lambda o: o.__dict__)
print(json_data)
# De-serialization
decoded_doc = Doc(**json.loads(json_data))
print(decoded_doc)
print(vars(decoded_doc))
Это выведет следующее:
{"abc": {"name": "abc name"}}
<__main__.Doc object at 0x7ff75366f250>
{'abc': {'name': 'abc name'}}
Вся магия работает путем определения лямбда-функции по умолчанию:
json_data = json.dumps(doc, default=lambda o: o.__dict__)
.
Для более сложной сериализации я бы использовал jsons, он был опубликован в 2022 году.
Превратите объекты Python в dicts или (JSON)строки и обратно
Никаких изменений в ваших объектах не требуется
Легко настраиваемый и расширяемый
Работает с классами данных, атрибутами и POPO.
pip install jsons class Person: name:str birthday:datetime personObject = Person("Tony", date_of_birth) import jsons json_data = jsons.dumps(personObject, indent=4)
Я не мог добавить это как комментарий и добавление как ответ. Последний пример Фреда был полезен для меня. Мне сказали, что jsonpickle делает это, но не смог заставить модуль установить и работать должным образом. Так что использовал код здесь. Небольшая настройка, однако, у меня было слишком много переменных, чтобы добавить их вручную к некоторым объектам. Итак, этот маленький цикл упростил вещи:
def reprJSON(self):
d = dict()
for a, v in self.__dict__.items():
if (hasattr(v, "reprJSON")):
d[a] = v.reprJSON()
else:
d[a] = v
return d
Он может использоваться в любом объекте, который имеет подкласс, который слишком занят для ручного кодирования. Или можно сделать помощником для всех классов. Это также работает для полной JSON-презентации массивов-членов, содержащих другие классы (если, конечно, они реализуют reprJSON()).
Это то, что вы ищете: https://github.com/jsonpickle/jsonpickle
Он выполняет вложенную сериализацию объектов Python и может быть легко расширен для сериализации пользовательских типов.