¿Cuál es la forma recomendada de serializar un namedtuple
a json con los nombres de campo retenidos?
Serializar un namedtuple
a json da como resultado que solo se serialicen los valores y que los nombres de los campos se pierdan en la traducción. Me gustaría que los campos también se conserven cuando json-ized y, por lo tanto, hice lo siguiente:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
Lo anterior se serializa en json como espero y se comporta como namedtuple
en otros lugares que uso (acceso a atributos, etc.) excepto con resultados que no son de tupla mientras lo itera (lo cual está bien para mi caso de uso).
¿Cuál es la "forma correcta" de convertir a json con los nombres de campo retenidos?
python
json
namedtuple
calvinkrishy
fuente
fuente
Respuestas:
Esto es bastante complicado, ya que
namedtuple()
es una fábrica que devuelve un nuevo tipo derivado detuple
. Un enfoque sería hacer que su clase también heredeUserDict.DictMixin
, perotuple.__getitem__
ya está definida y espera un número entero que denote la posición del elemento, no el nombre de su atributo:>>> f = foobar('a', 1) >>> f[0] 'a'
En el fondo, namedtuple es un ajuste extraño para JSON, ya que en realidad es un tipo personalizado cuyos nombres de clave se fijan como parte de la definición de tipo , a diferencia de un diccionario donde los nombres de clave se almacenan dentro de la instancia. Esto evita que usted "haga un viaje redondo" a una tupla con nombre, por ejemplo, no puede decodificar un diccionario en una tupla con nombre sin alguna otra información, como un marcador de tipo específico de aplicación en el dict
{'a': 1, '#_type': 'foobar'}
, que es un poco hacky.Esto no es ideal, pero si solo necesita codificar tuplas con nombre en diccionarios, otro enfoque es extender o modificar su codificador JSON para casos especiales de estos tipos. Aquí hay un ejemplo de subclases de Python
json.JSONEncoder
. Esto aborda el problema de garantizar que las tuplas con nombre anidadas se conviertan correctamente en diccionarios: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"}}
fuente
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder)
<<< '["x", "y"]'
Si es solo uno
namedtuple
que está buscando serializar, usar su_asdict()
método funcionará (con 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}'
fuente
fb._asdict()
ovars(fb)
sería mejor.vars
en un objeto sin un__dict__
.__dict__
en esos. =)__dict__
se ha eliminado._asdict
parece funcionar en ambos.Parece que solía ser capaz de
simplejson.JSONEncoder
crear una subclase para que esto funcione, pero con el último código simplejson, ese ya no es el caso: tiene que modificar el código del proyecto. No veo ninguna razón por la que simplejson no deba admitir namedtuples, así que bifurqué el proyecto, agregué el soporte namedtuple y actualmente estoy esperando que mi rama vuelva al proyecto principal . Si necesita las correcciones ahora, simplemente tire de mi tenedor.EDITAR : Parece que las últimas versiones de
simplejson
ahora admiten esto de forma nativa con lanamedtuple_as_object
opción, que por defecto esTrue
.fuente
ujson
, que es aún más extraño e impredecible en tales casos extremos ...simplejson.dumps(my_tuple, indent=4)
Escribí una biblioteca para hacer esto: https://github.com/ltworf/typedload
Puede ir desde y hacia la tupla con nombre y viceversa.
Admite estructuras anidadas bastante complicadas, con listas, conjuntos, enumeraciones, uniones, valores predeterminados. Debería cubrir los casos más comunes.
editar: la biblioteca también admite clases de datos y atributos.
fuente
Convierte de forma recursiva los datos namedTuple a 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'}
fuente
Hay una solución más conveniente que es usar el decorador (usa el 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))
fuente
_asdict
, que también es un miembro de clase "protegido"._fields
;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py Es parte de la API pública de namedtuple, en realidad: docs.python.org/3.7/library/ ... La gente se confunde con el subrayado (¡no es de extrañar!). Es un mal diseño, pero no sé qué otra opción tenían.La biblioteca jsonplus proporciona un serializador para instancias de NamedTuple. Use su modo de compatibilidad para generar objetos simples si es necesario, pero prefiera el predeterminado, ya que es útil para decodificar.
fuente
.dumps()
y.loads()
sin configuración, simplemente funciona.Es imposible serializar las tuplas con nombre correctamente con la biblioteca json nativa de Python. Siempre verá las tuplas como listas, y es imposible anular el serializador predeterminado para cambiar este comportamiento. Es peor si los objetos están anidados.
Es mejor usar una biblioteca más 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 }
fuente
orjson
también soy fanático .Ésta es una vieja pregunta. Sin embargo:
Una sugerencia para todos aquellos con la misma pregunta, piensen detenidamente sobre el uso de cualquiera de las características privadas o internas del
NamedTuple
porque tienen antes y volverán a cambiar con el tiempo.Por ejemplo, si su
NamedTuple
es un objeto de valor plano y solo está interesado en serializarlo y no en los casos en que esté anidado en otro objeto, podría evitar los problemas que surgirían al__dict__
eliminarlo o_as_dict()
cambiarlo y simplemente hacer algo como (y sí, esto es Python 3 porque esta respuesta es para el 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())
Intenté usar el
default
kwarg invocabledumps
para hacer lato_dict()
llamada si estaba disponible, pero no se llamó porqueNamedTuple
es convertible a una lista.fuente
_asdict
es parte de la API pública namedtuple. Explican el motivo del guión bajo docs.python.org/3.7/library/… "Además de los métodos heredados de las tuplas, las tuplas con nombre admiten tres métodos adicionales y dos atributos. Para evitar conflictos con los nombres de campo, los nombres de método y atributo comience con un guión bajo ".Aquí está mi opinión sobre el problema. Serializa NamedTuple, se encarga de las listas y de las NamedTuple plegadas dentro de ellas
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
fuente
simplejson.dump()
en lugar dejson.dump
hacer el trabajo. Aunque puede ser más lento.fuente