Каков рекомендуемый способ сериализации a namedtuple
в json с сохранением имен полей?
Сериализация a namedtuple
в json приводит к сериализации только значений, а имена полей теряются при переводе. Я хотел бы, чтобы поля также сохранялись при json-ized и, следовательно, сделал следующее:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
Вышеупомянутое сериализуется в json, как я ожидал, и ведет себя так же, как и namedtuple
в других местах, которые я использую (доступ к атрибутам и т. Д.), За исключением результатов, отличных от кортежа, при его повторении (что хорошо для моего варианта использования).
Каков «правильный способ» преобразования в json с сохранением имен полей?
python
json
namedtuple
Calvinkrishy
источник
источник
Ответы:
Это довольно сложно, поскольку
namedtuple()
это фабрика, возвращающая новый тип, производный отtuple
. Один из подходов заключается в том, чтобы ваш класс также унаследовал отUserDict.DictMixin
, ноtuple.__getitem__
уже определен и ожидает целое число, обозначающее позицию элемента, а не имя его атрибута:>>> f = foobar('a', 1) >>> f[0] 'a'
По сути, namedtuple не подходит для JSON, поскольку на самом деле это настраиваемый тип, имена ключей которого фиксируются как часть определения типа , в отличие от словаря, в котором имена ключей хранятся внутри экземпляра. Это предохраняет вас от «кругового обхода» именованного кортежа, например, вы не можете декодировать словарь обратно в именованный кортеж без какой-либо другой части информации, такой как маркер типа для конкретного приложения в dict
{'a': 1, '#_type': 'foobar'}
, что немного взломано.Это не идеально, но если вам нужно только кодировать именованные кортежи в словари, другой подход состоит в том, чтобы расширить или изменить кодировщик JSON для особых случаев этих типов. Вот пример подкласса Python
json.JSONEncoder
. Это решает проблему обеспечения правильного преобразования вложенных именованных кортежей в словари: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"}}
источник
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder)
<<< '["x", "y"]'
Если это только тот, который
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}'
источник
fb._asdict()
илиvars(fb)
было бы лучше.vars
объект без__dict__
.__dict__
их. =)__dict__
был удален._asdict
похоже, работает на обоих.Похоже, что раньше вы могли создать подкласс
simplejson.JSONEncoder
для выполнения этой работы, но с последним кодом simplejson это уже не так: вам нужно фактически изменить код проекта. Я не вижу причин, по которым simplejson не должен поддерживать namedtuples, поэтому я разветвил проект, добавил поддержку namedtuple и в настоящее время жду, когда моя ветка будет возвращена в основной проект . Если вам нужны исправления сейчас, просто снимите с моей вилки.РЕДАКТИРОВАТЬ : Похоже, что последние версии
simplejson
теперь изначально поддерживают это сnamedtuple_as_object
опцией, которая по умолчаниюTrue
.источник
ujson
, что еще более странно и непредсказуемо в таких крайних случаях ...simplejson.dumps(my_tuple, indent=4)
Я написал для этого библиотеку: https://github.com/ltworf/typedload
Он может переходить от и к именованному кортежу и обратно.
Он поддерживает довольно сложные вложенные структуры со списками, наборами, перечислениями, объединениями и значениями по умолчанию. Он должен охватывать наиболее распространенные случаи.
edit: библиотека также поддерживает классы dataclass и attr.
источник
Он рекурсивно преобразует данные namedTuple в json.
print(m1) ## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='2@mai.com'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='2@mai.com', 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': '2@mai.com', 'id': 1}, 'content': 'text', 'customer': {'first_name': 'asd', 'last_name': 'asd', 'mail': '2@mai.com', 'phone_number': 123123, 'id': 1}, 'id': 2, 'la': 123123, 'ls': 4512313, 'media_url': 'h.com', 'type': 'image'}
источник
Есть более удобное решение - использовать декоратор (он использует защищенное поле
_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))
источник
_asdict
, который также является «защищенным» членом класса._fields
;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py Это часть общедоступного API namedtuple , на самом деле: docs.python.org/3.7/library/… Люди запутались подчеркивание (неудивительно!). Плохой дизайн, но я не знаю, какой у них был выбор.Библиотека jsonplus предоставляет сериализатор для экземпляров NamedTuple. При необходимости используйте его режим совместимости для вывода простых объектов, но предпочитайте режим по умолчанию, поскольку он полезен для обратного декодирования.
источник
.dumps()
и.loads()
без конфигурации он просто работает.Невозможно правильно сериализовать именованные кортежи с помощью собственной библиотеки json python. Он всегда будет видеть кортежи в виде списков, и невозможно изменить сериализатор по умолчанию, чтобы изменить это поведение. Хуже, если объекты вложены друг в друга.
Лучше использовать более надежную библиотеку, например orjson :
import orjson from typing import NamedTuple class Rectangle(NamedTuple): width: int height: int def default(obj): if hasattr(obj, '_asdict'): return obj._asdict() rectangle = Rectangle(width=10, height=20) print(orjson.dumps(rectangle, default=default))
=>
{ "width":10, "height":20 }
источник
orjson
тоже фанат .Это старый вопрос. Тем не мение:
Предложение для всех, у кого есть один и тот же вопрос: хорошо подумайте об использовании каких-либо частных или внутренних функций
NamedTuple
потому что они были раньше и со временем будут меняться снова.Например, если ваш
NamedTuple
объект с плоскими значениями и вы заинтересованы только в его сериализации, а не в тех случаях, когда он вложен в другой объект, вы можете избежать проблем, связанных с__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
вызываемый kwargdumps
, чтобы выполнитьto_dict()
вызов, если он доступен, но он не был вызван, поскольку егоNamedTuple
можно преобразовать в список.источник
_asdict
является частью общедоступного API namedtuple. Они объясняют причину подчеркивания docs.python.org/3.7/library/… «Помимо методов, унаследованных от кортежей, именованные кортежи поддерживают три дополнительных метода и два атрибута. Чтобы предотвратить конфликты с именами полей, именами методов и атрибутов начинаются с подчеркивания ".Вот мой взгляд на проблему. Он сериализует NamedTuple, заботится о свернутых NamedTuples и списках внутри них.
def recursive_to_dict(obj: Any) -> dict: _dict = {} if isinstance(obj, tuple): node = obj._asdict() for item in node: if isinstance(node[item], list): # Process as a list _dict[item] = [recursive_to_dict(x) for x in (node[item])] elif getattr(node[item], "_asdict", False): # Process as a NamedTuple _dict[item] = recursive_to_dict(node[item]) else: # Process as a regular element _dict[item] = (node[item]) return _dict
источник
simplejson.dump()
вместо тогоjson.dump
, чтобы делать работу. Хотя это может быть медленнее.источник