Сериализация именованного корня Python в json - PullRequest
65 голосов
/ 06 мая 2011

Каков рекомендуемый способ сериализации namedtuple в json с сохранением имен полей?

Сериализация namedtuple в json приводит к тому, что сериализуются только значения, а имена полей теряются при переводе. Я хотел бы, чтобы поля также были сохранены при json-ized и, следовательно, сделали следующее:

class foobar(namedtuple('f', 'foo, bar')):
    __slots__ = ()
    def __iter__(self):
        yield self._asdict()

Вышеизложенное сериализует в json, как я ожидаю, и ведет себя как namedtuple в других местах, которые я использую (доступ к атрибутам и т. Д.), За исключением результатов, не связанных с кортежем, при итерации (что хорошо для моего варианта использования). 1011 *

Каков «правильный» способ преобразования в json с сохранением имен полей?

Ответы [ 8 ]

53 голосов
/ 04 апреля 2013

Если это просто один 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}'
47 голосов
/ 06 мая 2011

Это довольно сложно, поскольку namedtuple() - это фабрика, которая возвращает новый тип, полученный из tuple. Один из подходов состоит в том, чтобы ваш класс также наследовал от UserDict.DictMixin, но tuple.__getitem__ уже определен и ожидает целое число, обозначающее положение элемента, а не имя его атрибута:

>>> f = foobar('a', 1)
>>> f[0]
'a'

В своей основе namedtuple является нечетным соответствием для JSON, поскольку это действительно пользовательский тип, имена ключей которого фиксированы как часть определения типа , в отличие от словаря, в котором хранятся имена ключей внутри экземпляра. Это предотвращает «круговое отключение» именованного кортежа, например, Вы не можете декодировать словарь обратно в именованный кортеж без какой-либо другой части информации, например, маркера типа для конкретного приложения в dict {'a': 1, '#_type': 'foobar'}, что немного хакерски.

Это не идеально, но , если вам нужно только кодировать namedtuples в словари, другой подход заключается в расширении или изменении вашего 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"}}
20 голосов
/ 04 июля 2011

Похоже, вы привыкли иметь подкласс simplejson.JSONEncoder, чтобы сделать эту работу, но с последним кодом simplejson это уже не так: вам нужно фактически изменить код проекта. Я не вижу причин, по которым simplejson не должен поддерживать namedtuples, поэтому я раздвоил проект, добавил поддержку namedtuple, и я в настоящее время жду, когда моя ветвь будет возвращена в основной проект . Если вам нужны исправления сейчас, просто вытащите из моей вилки.

EDIT : похоже, что последние версии simplejson теперь изначально поддерживают это с опцией namedtuple_as_object, которая по умолчанию True.

4 голосов
/ 08 апреля 2018

Я написал библиотеку для этого: https://github.com/ltworf/typedload

Он может идти от и к именованному кортежу и обратно.

Он поддерживает довольно сложные вложенные структуры со списками, наборами,перечисления, объединения, значения по умолчанию.Он должен охватывать большинство распространенных случаев.

edit: библиотека также поддерживает классы данных и классы attr.

1 голос
/ 07 сентября 2018

Существует более удобное решение - использовать декоратор (используется защищенное поле _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))
1 голос
/ 22 марта 2018

Рекурсивно преобразует данные 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'}
0 голосов
/ 01 июля 2019

Библиотека jsonplus предоставляет сериализатор для экземпляров NamedTuple.Используйте режим совместимости для вывода простых объектов, если это необходимо, но предпочитайте значения по умолчанию, поскольку это полезно для декодирования обратно.

0 голосов
/ 29 ноября 2018

Это старый вопрос. Тем не менее:

Предложение для всех тех, у кого один и тот же вопрос. Тщательно подумайте об использовании каких-либо частных или внутренних функций 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 вызываемый kwarg для dumps, чтобы сделать вызов to_dict(), если он доступен, но он не был вызван, поскольку NamedTuple можно преобразовать в список.

...