Сериализация Python namedtuple для json
Каков рекомендуемый способ сериализации namedtuple
для json с сохраненными именами полей?
Сериализация a namedtuple
to json приводит только к значениям, которые сериализуются, и имена полей теряются при переводе. Я бы хотел, чтобы поля также сохранялись при json-ized и, следовательно, выполняли следующее:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
Вышеприведенный сериализуется в json, как я ожидаю, и ведет себя как namedtuple
в других местах, которые я использую (доступ к атрибутам и т.д.), за исключением тех случаев, когда они не имеют кортежей, когда они повторяются (что отлично подходит для моего варианта использования).
Что такое "правильный способ" преобразования в json с сохраненными именами полей?
Ответы
Ответ 1
Это довольно сложно, поскольку namedtuple()
- это factory, который возвращает новый тип, полученный из tuple
. Один из подходов заключался бы в том, чтобы ваш класс также наследовал от UserDict.DictMixin
, но tuple.__getitem__
уже определен и ожидает целое число, обозначающее позицию элемента, а не имя его атрибута:
>>> f = foobar('a', 1)
>>> f[0]
'a'
По сути, namedtuple является нечетным положением для JSON, поскольку он действительно является настраиваемым типом, имена ключей которого фиксированы как часть определения типа, в отличие от словаря, где имена ключей хранится внутри экземпляра. Это предотвращает "круговое отключение" именованного набора, например. вы не можете декодировать словарь обратно в namedtuple без какой-либо другой части информации, такой как маркер типа приложения в dict {'a': 1, '#_type': 'foobar'}
, который немного взломан.
Это не идеально, но , если вам нужно только кодировать namedtuples в словари, другой подход заключается в расширении или модификации вашего JSON-кодера в специальных случаях этих типов. Ниже приведен пример подклассов Python json.JSONEncoder
. Это решает проблему обеспечения правильного преобразования вложенных namedtuples в словари:
from collections import namedtuple
from json import JSONEncoder
class MyEncoder(JSONEncoder):
def _iterencode(self, obj, markers=None):
if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
gen = self._iterencode_dict(obj._asdict(), markers)
else:
gen = JSONEncoder._iterencode(self, obj, markers)
for chunk in gen:
yield chunk
class foobar(namedtuple('f', 'foo, bar')):
pass
enc = MyEncoder()
for obj in (foobar('a', 1), ('a', 1), {'outer': foobar('x', 'y')}):
print enc.encode(obj)
{"foo": "a", "bar": 1}
["a", 1]
{"outer": {"foo": "x", "bar": "y"}}
Ответ 2
Если это всего лишь один namedtuple
, который вы ищете для сериализации, использование его метода _asdict()
будет работать (с Python >= 2.7)
>>> from collections import namedtuple
>>> import json
>>> FB = namedtuple("FB", ("foo", "bar"))
>>> fb = FB(123, 456)
>>> json.dumps(fb._asdict())
'{"foo": 123, "bar": 456}'
Ответ 3
Похоже, что вы использовали подкласс simplejson.JSONEncoder
для выполнения этой работы, но с последним простым словом, это уже не так: вы должны действительно изменить код проекта. Я не вижу причин, почему simplejson не должен поддерживать namedtuples, поэтому я разветвлял проект, добавлял поддержку namedtuple, и я в настоящее время ждет, когда моя ветка вернется в главный проект. Если вам нужны исправления, просто вытащите из вилки.
EDIT. Похоже, что последние версии simplejson
теперь поддерживают это с помощью опции namedtuple_as_object
, которая по умолчанию имеет значение True
.
Ответ 4
Я написал библиотеку для этого: https://github.com/ltworf/typedload
Это может идти от и до именованного кортежа и обратно.
Он поддерживает довольно сложные вложенные структуры со списками, множествами, перечислениями, объединениями, значениями по умолчанию. Он должен охватывать наиболее распространенные случаи.
edit: библиотека также поддерживает классы данных и классы attr.
Ответ 5
Он рекурсивно преобразует данные namedTuple в json.
print(m1)
## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='[email protected]'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='[email protected]', phone_number=123123), type='image', content='text', media_url='h.com', la=123123, ls=4512313)
def reqursive_to_json(obj):
_json = {}
if isinstance(obj, tuple):
datas = obj._asdict()
for data in datas:
if isinstance(datas[data], tuple):
_json[data] = (reqursive_to_json(datas[data]))
else:
print(datas[data])
_json[data] = (datas[data])
return _json
data = reqursive_to_json(m1)
print(data)
{'agent': {'first_name': 'asd',
'last_name': 'asd',
'mail': '[email protected]',
'id': 1},
'content': 'text',
'customer': {'first_name': 'asd',
'last_name': 'asd',
'mail': '[email protected]',
'phone_number': 123123,
'id': 1},
'id': 2,
'la': 123123,
'ls': 4512313,
'media_url': 'h.com',
'type': 'image'}
Ответ 6
Есть более удобное решение - использовать декоратор (он использует защищенное поле _fields
).
Python 2. 7+:
import json
from collections import namedtuple, OrderedDict
def json_serializable(cls):
def as_dict(self):
yield OrderedDict(
(name, value) for name, value in zip(
self._fields,
iter(super(cls, self).__iter__())))
cls.__iter__ = as_dict
return cls
#Usage:
C = json_serializable(namedtuple('C', 'a b c'))
print json.dumps(C('abc', True, 3.14))
# or
@json_serializable
class D(namedtuple('D', 'a b c')):
pass
print json.dumps(D('abc', True, 3.14))
Python 3.6. 6+:
import json
from typing import TupleName
def json_serializable(cls):
def as_dict(self):
yield {name: value for name, value in zip(
self._fields,
iter(super(cls, self).__iter__()))}
cls.__iter__ = as_dict
return cls
# Usage:
@json_serializable
class C(NamedTuple):
a: str
b: bool
c: float
print(json.dumps(C('abc', True, 3.14))
Ответ 7
Это старый вопрос. Тем не мение:
Предложение для всех тех, у кого один и тот же вопрос, тщательно подумайте об использовании каких-либо частных или внутренних функций NamedTuple
потому что они были раньше и будут меняться со временем.
Например, если ваш NamedTuple
является объектом с плоским значением и вы заинтересованы только в его сериализации, а не в случаях, когда он вложен в другой объект, вы можете избежать проблем, которые могут __dict__
к удалению _as_dict()
или изменению _as_dict()
и просто сделайте что-то вроде (и да, это Python 3, потому что этот ответ на данный момент):
from typing import NamedTuple
class ApiListRequest(NamedTuple):
group: str="default"
filter: str="*"
def to_dict(self):
return {
'group': self.group,
'filter': self.filter,
}
def to_json(self):
return json.dumps(self.to_dict())
Я попытался использовать вызываемый по default
kwarg для dumps
, чтобы сделать to_dict()
если он доступен, но его не вызывали, поскольку NamedTuple
можно преобразовать в список.
Ответ 8
Библиотека jsonplus предоставляет сериализатор для экземпляров NamedTuple. Используйте режим совместимости для вывода простых объектов, если это необходимо, но предпочитайте значения по умолчанию, так как это полезно для обратного декодирования.