Serialisierung eines Python-Namenstupels zu json


85

Was ist die empfohlene Methode zum Serialisieren von a namedtuplenach json, wobei die Feldnamen beibehalten werden?

Das Serialisieren von a namedtuplenach 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 namedtuplean 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?


Antworten:


56

Dies ist ziemlich schwierig, da namedtuple()es sich um eine Factory handelt, die einen neuen Typ zurückgibt, der von abgeleitet ist tuple. Ein Ansatz wäre, dass Ihre Klasse auch von erbt UserDict.DictMixin, aber tuple.__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"}}

12
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. Sehr aufschlussreicher Kommentar. Daran hatte ich nicht gedacht. Vielen Dank. Ich mag namedtuples, da sie eine schöne unveränderliche Struktur mit Komfort für die Benennung von Attributen bieten . Ich werde Ihre Antwort akzeptieren. Trotzdem bietet der Serialisierungsmechanismus von Java mehr Kontrolle darüber, wie das Objekt serialisiert wird, und ich bin gespannt, warum solche Hooks in Python nicht zu existieren scheinen.
Calvinkrishy

Das war mein erster Ansatz, aber er funktioniert nicht wirklich (für mich jedenfalls).
Zeekay

1
>>> json.dumps(foobar('x', 'y'), cls=MyEncoder) <<< '["x", "y"]'
Zeekay

19
Ah, in Python 2.7+ ist _iterencode keine Methode von JSONEncoder mehr.
Zeekay

2
@calvin Danke, ich finde das namedtuple auch nützlich. Ich wünschte, es gäbe eine bessere Lösung, um es rekursiv in JSON zu codieren. @zeekay Yep, scheint in 2.7+ sie verstecken es so, dass es nicht mehr überschrieben werden kann. Das ist enttäuschend.
samplebias

77

Wenn es nur eine ist, die namedtupleSie 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}'

4
Ich erhalte AttributeError: Das Objekt 'FB' hat kein Attribut ' dict ', wenn dieser Code in Python 2.7 (x64) unter Windows ausgeführt wird. Fb._asdict () funktioniert jedoch einwandfrei.
Geographika

5
fb._asdict()oder vars(fb)wäre besser.
jpmc26

1
@ jpmc26: Sie können nicht varsfür ein Objekt ohne ein verwenden __dict__.
Rufflewind

@Rufflewind Sie können diese auch nicht verwenden __dict__. =)
jpmc26

4
In Python 3 __dict__wurde entfernt. _asdictscheint auf beiden zu funktionieren.
Andy Hayden

21

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 simplejsonjetzt dies nativ mit der namedtuple_as_objectOption, die standardmäßig verwendet wird True.


3
Ihre Bearbeitung ist die richtige Antwort. simplejson serialisiert namedtuples anders (meiner Meinung nach: besser) als json. Dies macht das Muster: "try: importiere simplejson als json außer: importiere json" wirklich riskant, da auf einigen Computern je nach Installation von simplejson möglicherweise ein anderes Verhalten auftritt. Aus diesem Grund benötige ich jetzt simplejson in vielen meiner Setup-Dateien und verzichte auf dieses Muster.
März 75

1
@ marr75 - Ditto für ujson, was in solchen Randfällen noch bizarrer und unvorhersehbarer ist ...
Mac

Ich war in der Lage, ein rekursives Nameduple zu erhalten, das auf (hübsch gedrucktes) json serialisiert wurde, indem:simplejson.dumps(my_tuple, indent=4)
KFL

5

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.


2

Es konvertiert rekursiv die namedTuple-Daten in 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'}

1
+1 Ich habe fast das gleiche gemacht. Aber Ihre Rückkehr ist ein Diktat, nicht json. Sie müssen "nicht" haben, und wenn ein Wert in Ihrem Objekt ein Boolescher Wert ist, wird er nicht in "wahr" umgewandelt. Ich denke, es ist sicherer, ihn in "dikt" umzuwandeln. Verwenden Sie dann "json.dumps", um ihn in "json" umzuwandeln.
Fred Laurent

2

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))

Tun Sie das nicht, sie ändern die interne API ständig. Meine typedload-Bibliothek enthält mehrere Fälle für verschiedene py-Versionen.
LtWorf

Ja, es ist klar. Niemand sollte jedoch ohne Test auf eine neuere Python-Version migrieren. Und die anderen Lösungen verwenden _asdict, die auch ein "geschütztes" Klassenmitglied ist.
Dmitry T.

1
LtWorf, Ihre Bibliothek ist GPL und funktioniert nicht mit Frozensets
Thomas Grainger

2
@LtWorf Ihre Bibliothek verwendet auch _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.
quant_dev

1
Welche Sachen? Wann? Können Sie Versionshinweise zitieren?
quant_dev

2

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.


Ich habe mir die anderen Lösungen hier angesehen und festgestellt, dass mir das Hinzufügen dieser Abhängigkeit viel Zeit gespart hat. Vor allem, weil ich eine Liste von NamedTuples hatte, die ich in der Sitzung als json übergeben musste. Mit jsonplus können Sie im Grunde genommen Listen mit benannten Tupeln in und aus json abrufen, .dumps()und .loads()ohne Konfiguration funktioniert es einfach.
Rob

0

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, NamedTupleda diese bereits vorhanden sind und sich im Laufe der Zeit erneut ändern werden.

Wenn es sich beispielsweise NamedTupleum 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 defaultaufrufbare kwarg zu dumpsverwenden, um den to_dict()Anruf zu tätigen, falls verfügbar, aber das wurde nicht aufgerufen, da das NamedTuplein eine Liste konvertierbar ist.


3
_asdictist 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. "
quant_dev

@quant_dev danke, ich habe diese Erklärung nicht gesehen. Es ist keine Garantie für API-Stabilität, aber es hilft, diese Methoden vertrauenswürdiger zu machen. Ich mag die explizite Lesbarkeit von to_dict, aber ich kann sehen, dass es so aussieht, als würde ich _as_dict
dlamblin

0

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

0

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
}

0

simplejson.dump()statt json.dumpmacht den Job. Es kann jedoch langsamer sein.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.