Сериализация Python namedtuple в json
каков рекомендуемый способ сериализации a namedtuple в json с сохраненными именами полей?
сериализация a namedtuple в json приводит только к сериализации значений и потере имен полей при переводе. Я хотел бы, чтобы поля также сохранялись при json-ized и, следовательно, делали следующее:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
выше сериализуется в json, как я ожидаю, и ведет себя как namedtuple в других местах я использую (доступ к атрибутам и т. д.,) за исключением не Кортеж, как результаты при итерации его (что хорошо для моего случая использования).
каков "правильный способ" преобразования в json с сохраненными именами полей?
6 ответов:
это довольно сложно, поскольку
namedtuple()- это фабрика, которая возвращает новый тип, производный отtuple. Одним из подходов было бы иметь ваш класс также наследовать отUserDict.DictMixin, аtuple.__getitem__уже определен и ожидает целое число, обозначающее положение элемента, а не имя Его атрибутов:>>> f = foobar('a', 1) >>> f[0] 'a'в своей основе namedtuple странно подходит для JSON, так как это действительно пользовательский тип, имена ключей которого фиксируются как часть определения типа, в отличие от словаря, где имена ключей хранятся внутри экземпляра. Это предотвращает "круговое отключение" namedtuple, например, вы не можете декодировать словарь обратно в 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"}}
Если это только один
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}'
похоже, что вы раньше могли подкласс
simplejson.JSONEncoderчтобы сделать эту работу, но с последним кодом simplejson, это уже не так: вы должны фактически изменить код проекта. Я не вижу причин, почему simplejson не должен поддерживать namedtuples, поэтому я разветвил проект, добавил поддержку namedtuple, и я в настоящее время жду, когда моя ветка будет возвращена в основной проект. Если вам нужны исправления сейчас, просто потяните от моей вилки.EDIT: Похоже на последние версии
simplejsonтеперь поддерживает с по умолчанию:True.
Я написал библиотеку для этого:https://github.com/ltworf/typedload
Он может идти от и до именованного кортежа и обратно.
Он поддерживает довольно сложные вложенные структуры, со списками, наборами, перечислениями, объединениями, значениями по умолчанию. Он должен охватывать наиболее распространенные случаи.
он рекурсивно преобразует данные 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'}
есть и более удобное решение-использовать декоратор (он использует защищенное поле
_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))
Comments