Qual é a maneira recomendada de serializar um namedtuple
para json com os nomes de campo retidos?
Serializar a namedtuple
para json resulta na serialização apenas dos valores e na perda dos nomes dos campos na tradução. Eu gostaria que os campos também fossem mantidos quando jsonizados e, portanto, fiz o seguinte:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
O código acima é serializado para json conforme eu espero e se comporta como namedtuple
em outros lugares que eu uso (acesso de atributo, etc.), exceto com resultados não-tupla durante a iteração (o que é bom para meu caso de uso).
Qual é a "maneira correta" de converter para JSON com os nomes dos campos retidos?
python
json
namedtuple
calvinkrishy
fonte
fonte
Respostas:
Isso é bastante complicado, pois
namedtuple()
é uma fábrica que retorna um novo tipo derivado detuple
. Uma abordagem seria fazer com que sua classe também herde deUserDict.DictMixin
, mastuple.__getitem__
já está definida e espera um número inteiro denotando a posição do elemento, não o nome de seu atributo:>>> f = foobar('a', 1) >>> f[0] 'a'
Em sua essência, o namedtuple é um ajuste estranho para JSON, pois é realmente um tipo customizado cujos nomes de chave são fixados como parte da definição de tipo , ao contrário de um dicionário onde os nomes de chave são armazenados dentro da instância. Isso evita que você faça um "round-trip" de um namedtuple, por exemplo, você não pode decodificar um dicionário de volta para um namedtuple sem alguma outra informação, como um marcador de tipo específico do aplicativo no dicionário
{'a': 1, '#_type': 'foobar'}
, que é um pouco hackeado.Isso não é o ideal, mas se você só precisa codificar namedtuples em dicionários, outra abordagem é estender ou modificar seu codificador JSON para casos especiais desses tipos. Aqui está um exemplo de subclasse de Python
json.JSONEncoder
. Isso resolve o problema de garantir que as duplicatas nomeadas aninhadas sejam convertidas corretamente em dicionários: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"}}
fonte
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder)
<<< '["x", "y"]'
Se for apenas um que
namedtuple
você deseja serializar, usar seu_asdict()
método funcionará (com 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}'
fonte
fb._asdict()
ouvars(fb)
seria melhor.vars
em um objeto sem um__dict__
.__dict__
. =)__dict__
foi removido._asdict
parece funcionar em ambos.Parece que você costumava ser capaz de criar uma subclasse
simplejson.JSONEncoder
para fazer isso funcionar, mas com o código simplejson mais recente, esse não é mais o caso: você realmente tem que modificar o código do projeto. Não vejo razão para que o simplejson não deva oferecer suporte a namedtuples, então eu fiz um fork do projeto, adicionei o suporte namedtuple e estou atualmente esperando que meu branch seja puxado de volta para o projeto principal . Se você precisar de correções agora, basta puxar do meu garfo.EDITAR : Parece que as versões mais recentes do
simplejson
agora suportam nativamente isso com anamedtuple_as_object
opção, cujo padrão éTrue
.fonte
ujson
, que é ainda mais bizarro e imprevisível em casos extremos ...simplejson.dumps(my_tuple, indent=4)
Eu escrevi uma biblioteca para fazer isso: https://github.com/ltworf/typedload
Ele pode ir de e para tupla nomeada e voltar.
Ele suporta estruturas aninhadas bastante complicadas, com listas, conjuntos, enums, uniões, valores padrão. Deve abranger os casos mais comuns.
editar: A biblioteca também oferece suporte a classes de dados e classes de atributos.
fonte
Ele converte recursivamente os dados namedTuple em 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'}
fonte
Existe uma solução mais conveniente é usar o decorador (ele usa o campo protegido
_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))
fonte
_asdict
, que também é um membro da classe "protegido"._fields
;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py Faz parte da API pública do namedtuple, na verdade: docs.python.org/3.7/library/… As pessoas ficam confusas com o sublinhado (não é de admirar!). É um projeto ruim, mas não sei que outra escolha eles tinham.A biblioteca jsonplus fornece um serializador para instâncias NamedTuple. Use seu modo de compatibilidade para gerar objetos simples, se necessário, mas prefira o padrão, pois é útil para a decodificação de volta.
fonte
.dumps()
e.loads()
sem configuração. Ele simplesmente funciona.É impossível serializar namedtuples corretamente com a biblioteca python json nativa. Ele sempre verá as tuplas como listas, e é impossível substituir o serializador padrão para alterar esse comportamento. É pior se os objetos estiverem aninhados.
Melhor usar uma biblioteca mais robusta como 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 }
fonte
orjson
.Esta é uma velha questão. Contudo:
Uma sugestão para todos aqueles com a mesma pergunta, pense cuidadosamente sobre como usar qualquer um dos recursos privados ou internos do
NamedTuple
porque eles já o fizeram e irão mudar novamente com o tempo.Por exemplo, se seu
NamedTuple
é um objeto de valor plano e você está interessado apenas em serializá-lo e não nos casos em que está aninhado em outro objeto, você pode evitar os problemas que surgiriam com__dict__
a remoção ou_as_dict()
alteração e apenas fazer algo como (e sim, este é Python 3 porque esta resposta é para o presente):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())
Tentei usar o
default
callable kwarg todumps
para fazer ato_dict()
chamada, se disponível, mas não foi chamado porqueNamedTuple
é conversível para uma lista.fonte
_asdict
faz parte da API pública namedtuple. Eles explicam o motivo do sublinhado docs.python.org/3.7/library/… "Além dos métodos herdados das tuplas, as tuplas nomeadas suportam três métodos adicionais e dois atributos. Para evitar conflitos com nomes de campo, o método e os nomes de atributo comece com um sublinhado. "Aqui está minha opinião sobre o problema. Ele serializa a NamedTuple, cuida das NamedTuples e Listas dobradas dentro delas
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
fonte
simplejson.dump()
em vez dejson.dump
fazer o trabalho. Pode ser mais lento.fonte