Was ist die empfohlene Methode zum Serialisieren von a namedtuple
nach json, wobei die Feldnamen beibehalten werden?
Das Serialisieren von a namedtuple
nach json führt dazu, dass nur die Werte serialisiert werden und die Feldnamen bei der Übersetzung verloren gehen. Ich möchte, dass die Felder auch bei der Json-Beibehaltung erhalten bleiben und daher Folgendes tun:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
Das Obige wird wie erwartet in json serialisiert und verhält sich wie namedtuple
an anderen Orten, die ich verwende (Attributzugriff usw.), außer mit nicht tupelähnlichen Ergebnissen beim Iterieren (was für meinen Anwendungsfall in Ordnung ist).
Was ist der "richtige Weg" zur Konvertierung in json unter Beibehaltung der Feldnamen?
python
json
namedtuple
calvinkrishy
quelle
quelle
Antworten:
Dies ist ziemlich schwierig, da
namedtuple()
es sich um eine Factory handelt, die einen neuen Typ zurückgibt, der von abgeleitet isttuple
. Ein Ansatz wäre, dass Ihre Klasse auch von erbtUserDict.DictMixin
, abertuple.__getitem__
bereits definiert ist und eine Ganzzahl erwartet, die die Position des Elements angibt, nicht den Namen seines Attributs:>>> f = foobar('a', 1) >>> f[0] 'a'
Im Kern passt das Namedtuple ungerade zu JSON, da es sich tatsächlich um einen benutzerdefinierten Typ handelt, dessen Schlüsselnamen als Teil der Typdefinition festgelegt sind , im Gegensatz zu einem Wörterbuch, in dem Schlüsselnamen in der Instanz gespeichert sind. Dies verhindert, dass Sie ein benanntes Tupel "umrunden", z. B. können Sie ein Wörterbuch nicht ohne andere Informationen wie ein app-spezifischer
{'a': 1, '#_type': 'foobar'}
Typmarker im Diktat, der etwas hackig ist, zurück in ein benanntes Tupel dekodieren.Dies ist nicht ideal, aber wenn Sie nur Namedtuples in Wörterbücher codieren müssen, besteht ein anderer Ansatz darin, Ihren JSON-Encoder zu erweitern oder zu ändern, um diese Typen in Sonderfällen zu verwenden. Hier ist ein Beispiel für die Unterklasse von Python
json.JSONEncoder
. Dies behebt das Problem, sicherzustellen, dass verschachtelte benannte Tupel ordnungsgemäß in Wörterbücher konvertiert werden: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"}}
quelle
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder)
<<< '["x", "y"]'
Wenn es nur eine ist, die
namedtuple
Sie serialisieren möchten,_asdict()
funktioniert die Verwendung der Methode (mit 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}'
quelle
fb._asdict()
odervars(fb)
wäre besser.vars
für ein Objekt ohne ein verwenden__dict__
.__dict__
. =)__dict__
wurde entfernt._asdict
scheint auf beiden zu funktionieren.Es sieht so aus, als ob Sie früher in der Lage waren, Unterklassen zu erstellen
simplejson.JSONEncoder
, damit dies funktioniert, aber mit dem neuesten simplejson-Code ist dies nicht mehr der Fall: Sie müssen den Projektcode tatsächlich ändern. Ich sehe keinen Grund, warum simplejson namedtuples nicht unterstützen sollte, also habe ich das Projekt gegabelt, namedtuple-Unterstützung hinzugefügt und bin es auch derzeit darauf, dass mein Zweig wieder in das Hauptprojekt aufgenommen wird . Wenn Sie jetzt die Korrekturen benötigen, ziehen Sie einfach von meiner Gabel.BEARBEITEN : Anscheinend unterstützen die neuesten Versionen von
simplejson
jetzt dies nativ mit dernamedtuple_as_object
Option, die standardmäßig verwendet wirdTrue
.quelle
ujson
, was in solchen Randfällen noch bizarrer und unvorhersehbarer ist ...simplejson.dumps(my_tuple, indent=4)
Ich habe dafür eine Bibliothek geschrieben: https://github.com/ltworf/typedload
Es kann von und zu Named-Tupel und zurück gehen.
Es unterstützt recht komplizierte verschachtelte Strukturen mit Listen, Mengen, Aufzählungen, Vereinigungen und Standardwerten. Es sollte die häufigsten Fälle abdecken.
Bearbeiten: Die Bibliothek unterstützt auch Datenklassen- und ATTR-Klassen.
quelle
Es konvertiert rekursiv die namedTuple-Daten in 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'}
quelle
Es gibt eine bequemere Lösung, den Dekorator zu verwenden (er verwendet das geschützte Feld
_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))
quelle
_asdict
, die auch ein "geschütztes" Klassenmitglied ist._fields
;-) github.com/ltworf/typedload/blob/master/typedload/datadumper.py Es ist Teil der öffentlichen API von namedtuple, eigentlich: docs.python.org/3.7/library/… Die Leute werden verwirrt von der Unterstrich (kein Wunder!). Es ist schlechtes Design, aber ich weiß nicht, welche andere Wahl sie hatten.Die jsonplus- Bibliothek bietet einen Serializer für NamedTuple-Instanzen. Verwenden Sie den Kompatibilitätsmodus, um bei Bedarf einfache Objekte auszugeben. Ziehen Sie jedoch die Standardeinstellung vor, da dies für die Rückcodierung hilfreich ist.
quelle
.dumps()
und.loads()
ohne Konfiguration funktioniert es einfach.Dies ist eine alte Frage. Jedoch:
Ein Vorschlag für alle, die dieselbe Frage haben. Überlegen Sie sorgfältig, ob Sie die privaten oder internen Funktionen von verwenden möchten,
NamedTuple
da diese bereits vorhanden sind und sich im Laufe der Zeit erneut ändern werden.Wenn es sich beispielsweise
NamedTuple
um ein Objekt mit flachem Wert handelt und Sie nur daran interessiert sind, es zu serialisieren, und nicht in Fällen, in denen es in einem anderen Objekt verschachtelt ist, können Sie die Probleme vermeiden, die beim__dict__
Entfernen oder_as_dict()
Ändern auftreten, und einfach so etwas tun (und ja, das ist Python 3, weil diese Antwort vorläufig ist):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())
Ich habe versucht, das
default
aufrufbare kwarg zudumps
verwenden, um dento_dict()
Anruf zu tätigen, falls verfügbar, aber das wurde nicht aufgerufen, da dasNamedTuple
in eine Liste konvertierbar ist.quelle
_asdict
ist Teil der öffentlichen API von namedtuple. Sie erläutern den Grund für den Unterstrich docs.python.org/3.7/library/… "Zusätzlich zu den von Tupeln geerbten Methoden unterstützen benannte Tupel drei zusätzliche Methoden und zwei Attribute. Um Konflikte mit Feldnamen zu vermeiden, werden die Methoden- und Attributnamen verwendet Beginnen Sie mit einem Unterstrich. "Hier ist meine Sicht auf das Problem. Es serialisiert das NamedTuple und kümmert sich um gefaltete NamedTuples und Listen in ihnen
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
quelle
Es ist unmöglich, Namedtuples mit der nativen Python-JSON-Bibliothek korrekt zu serialisieren. Tupel werden immer als Listen angezeigt, und es ist unmöglich, den Standard-Serializer zu überschreiben, um dieses Verhalten zu ändern. Es ist schlimmer, wenn Objekte verschachtelt sind.
Verwenden Sie besser eine robustere Bibliothek wie 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 }
quelle
simplejson.dump()
stattjson.dump
macht den Job. Es kann jedoch langsamer sein.quelle