Jaki jest zalecany sposób serializacji a namedtuple
do json z zachowanymi nazwami pól?
Serializacja a namedtuple
do json powoduje, że tylko wartości są serializowane, a nazwy pól są tracone podczas tłumaczenia. Chciałbym, aby pola również zostały zachowane po poddaniu ich jsonizowaniu, dlatego wykonałem następujące czynności:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
Powyższe serializuje się do json zgodnie z oczekiwaniami i zachowuje się tak, jak namedtuple
w innych miejscach, których używam (dostęp do atrybutów itp.), Z wyjątkiem wyników innych niż krotka podczas iteracji (co jest w porządku w moim przypadku użycia).
Jaki jest „prawidłowy sposób” konwertowania na json przy zachowaniu nazw pól?
python
json
namedtuple
calvinkrishy
źródło
źródło
Odpowiedzi:
Jest to dość trudne, ponieważ
namedtuple()
jest to fabryka, która zwraca nowy typ pochodzący ztuple
. Jedną z metod byłoby ustawienie klasy dziedziczącej także poUserDict.DictMixin
, aletuple.__getitem__
jest już zdefiniowana i oczekuje liczby całkowitej oznaczającej pozycję elementu, a nie nazwy jego atrybutu:>>> f = foobar('a', 1) >>> f[0] 'a'
W istocie namedtuple nie pasuje do JSON, ponieważ jest to w rzeczywistości niestandardowy typ, którego nazwy kluczy są ustalone jako część definicji typu , w przeciwieństwie do słownika, w którym nazwy kluczy są przechowywane wewnątrz instancji. Zapobiega to „przełączaniu w obie strony” nazwanego tulei, np. Nie można dekodować słownika z powrotem do nazwanego tulei bez innej informacji, takiej jak znacznik typu specyficznego dla aplikacji w dyktcie
{'a': 1, '#_type': 'foobar'}
, co jest nieco zepsute.Nie jest to idealne rozwiązanie, ale jeśli potrzebujesz tylko zakodować namedtuples w słownikach, innym podejściem jest rozszerzenie lub zmodyfikowanie kodera JSON do specjalnych przypadków tych typów. Oto przykład podklasy Pythona
json.JSONEncoder
. To rozwiązuje problem zapewnienia, że zagnieżdżone krotki nazw są prawidłowo konwertowane na słowniki: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"}}
źródło
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder)
<<< '["x", "y"]'
Jeśli to tylko jeden,
namedtuple
który chcesz serializować, użycie jego_asdict()
metody będzie działać (z Pythonem> = 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}'
źródło
fb._asdict()
lubvars(fb)
byłoby lepiej.vars
na obiekcie bez__dict__
.__dict__
na nich używać . =)__dict__
został usunięty._asdict
wydaje się działać na obu.Wygląda na to, że kiedyś można było tworzyć podklasy,
simplejson.JSONEncoder
aby to zadziałało, ale z najnowszym kodem simplejson już tak nie jest: musisz faktycznie zmodyfikować kod projektu. Nie widzę powodu, dla którego simplejson nie miałby obsługiwać namedtuples, więc rozwidliłem projekt, dodałem obsługę namedtuple i obecnie czekam, aż moja gałąź zostanie wciągnięta z powrotem do głównego projektu . Jeśli potrzebujesz teraz poprawek, po prostu wyciągnij z mojego widelca.EDYCJA : Wygląda na to, że najnowsze wersje
simplejson
teraz natywnie obsługują to znamedtuple_as_object
opcją, która domyślnieTrue
.źródło
ujson
, co jest jeszcze bardziej dziwaczne i nieprzewidywalne w takich skrajnych przypadkach ...simplejson.dumps(my_tuple, indent=4)
Napisałem w tym celu bibliotekę: https://github.com/ltworf/typedload
Może przechodzić zi do nazwanej krotki iz powrotem.
Obsługuje dość skomplikowane struktury zagnieżdżone, z listami, zestawami, wyliczeniami, sumami, wartościami domyślnymi. Powinien obejmować większość typowych przypadków.
edycja: biblioteka obsługuje również klasy dataclass i attr.
źródło
Rekurencyjnie konwertuje dane namedTuple na 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'}
źródło
Bardziej wygodnym rozwiązaniem jest użycie dekoratora (korzysta z chronionego pola
_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))
źródło
_asdict
, który jest również „chronionym” składnikiem klasy._fields
;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py Jest to część publicznego interfejsu API namedtuple , właściwie: docs.python.org/3.7/library/ ... Ludzie są zdezorientowani przez podkreślenie (nic dziwnego!). To zły projekt, ale nie wiem, jaki mieli inny wybór.Jsonplus biblioteka zapewnia serializatora dla instancji NamedTuple. Użyj jego trybu zgodności, aby w razie potrzeby wyprowadzić proste obiekty, ale preferuj domyślny, ponieważ jest pomocny przy dekodowaniu z powrotem.
źródło
.dumps()
i.loads()
bez config po prostu działa.Niemożliwe jest poprawne serializowanie namedtuples z natywną biblioteką json języka Python. Zawsze będzie widzieć krotki jako listy i nie można zastąpić domyślnego serializatora, aby zmienić to zachowanie. Gorzej, jeśli obiekty są zagnieżdżone.
Lepiej użyć bardziej niezawodnej biblioteki, takiej jak 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 }
źródło
orjson
.To jest stare pytanie. Jednak:
Sugestia dla wszystkich, którzy mają to samo pytanie, uważnie zastanówcie się nad wykorzystaniem jakichkolwiek prywatnych lub wewnętrznych cech tego,
NamedTuple
co mieli wcześniej i będą się zmieniać ponownie z czasem.Na przykład, jeśli twój
NamedTuple
jest obiektem o płaskiej wartości i jesteś zainteresowany tylko jego serializacją, a nie w przypadkach, gdy jest on zagnieżdżony w innym obiekcie, możesz uniknąć problemów, które pojawią się przy__dict__
usuwaniu lub_as_dict()
zmianie i po prostu zrobić coś takiego (i tak, to jest Python 3, ponieważ ta odpowiedź dotyczy teraźniejszości):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())
Próbowałem użyć
default
wywoływalnego kwarg todumps
, aby wykonaćto_dict()
wywołanie, jeśli jest dostępne, ale nie zostało to wywołane, ponieważNamedTuple
można je zamienić na listę.źródło
_asdict
jest częścią publicznego interfejsu API namedtuple. Wyjaśniają powód podkreślenia docs.python.org/3.7/library/ ... „Oprócz metod dziedziczonych z krotek, nazwane krotki obsługują trzy dodatkowe metody i dwa atrybuty. Aby zapobiec konfliktom z nazwami pól, nazwy metod i atrybutów zacznij od podkreślenia ”.Oto moje podejście do problemu. Serializuje NamedTuple, dba o zawinięte NamedTuples i listy wewnątrz nich
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
źródło
simplejson.dump()
zamiast wykonywaćjson.dump
swoją pracę. Może jednak działać wolniej.źródło