Klasseninstanz in JSON serialisieren

Lesezeit: 10 Minuten

Benutzer-Avatar
ferhan

Ich versuche, eine JSON-String-Darstellung einer Klasseninstanz zu erstellen, und habe Schwierigkeiten. Nehmen wir an, die Klasse ist wie folgt aufgebaut:

class testclass:
    value1 = "a"
    value2 = "b"

Ein Aufruf von json.dumps erfolgt wie folgt:

t = testclass()
json.dumps

Es schlägt fehl und sagt mir, dass die Testklasse nicht JSON-serialisierbar ist.

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable

Ich habe auch versucht, das Pickle-Modul zu verwenden:

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))

Und es gibt Klasseninstanzinformationen, aber keinen serialisierten Inhalt der Klasseninstanz.

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'

Was mache ich falsch?

  • stackoverflow.com/questions/2343535/…

    – CodeClown42

    20. April 2012 um 19:10 Uhr

  • Verwenden Sie eine Zeile, s = json.dumps(obj, default=lambda x: x.__dict__)um die Instanzvariablen des Objekts zu serialisieren (self.value1, self.value2, …). Es ist der einfachste und direkteste Weg. Es wird verschachtelte Objektstrukturen serialisieren. Die default Die Funktion wird aufgerufen, wenn ein bestimmtes Objekt nicht direkt serialisierbar ist. Sie können sich auch meine Antwort unten ansehen. Ich fand die populären Antworten unnötig komplex, die wahrscheinlich schon vor langer Zeit wahr waren.

    – codeman48

    13. Dezember 2017 um 7:25 Uhr

  • Dein testclass hat keine __init__() -Methode, sodass alle Instanzen dieselben zwei Klassenattribute (value1 und value2) in der class-Anweisung definiert. Verstehen Sie den Unterschied zwischen einer Klasse und einer Instanz einer Klasse?

    – Martinau

    30. Januar 2018 um 2:44 Uhr


  • Dafür gibt es eine Python-Bibliothek github.com/jsonpickle/jsonpickle (Kommentieren, da die Antwort zu unten im Thread ist und nicht erreichbar ist.)

    – schöne Grüße

    10. Mai 2019 um 4:54 Uhr


Benutzer-Avatar
steveha

Das grundlegende Problem ist, dass der JSON-Encoder json.dumps() weiß nur, wie standardmäßig eine begrenzte Menge von Objekttypen serialisiert wird, alle integrierten Typen. Liste hier: https://docs.python.org/3.3/library/json.html#encoders-and-decoders

Eine gute Lösung wäre, Ihre Klasse erben zu lassen JSONEncoder und dann die implementieren JSONEncoder.default() Funktion, und sorgen Sie dafür, dass diese Funktion das richtige JSON für Ihre Klasse ausgibt.

Eine einfache Lösung wäre anzurufen json.dumps() auf der .__dict__ Mitglied dieser Instanz. Das ist ein Standard-Python dict und wenn Ihre Klasse einfach ist, ist sie JSON-serialisierbar.

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

Der obige Ansatz wird in diesem Blogbeitrag diskutiert:

Serialisieren beliebiger Python-Objekte in JSON mit _Diktat_

Und natürlich bietet Python eine eingebaute Funktion, die darauf zugreift .__dict__ für dich, genannt vars().

Das obige Beispiel kann also auch so gemacht werden:

s = json.dumps(vars(foo)) # s set to: {"x":1, "y":2}

  • Ich habe das versucht. Das Endergebnis eines Aufrufs von json.dumps(t.__dict__) ist nur {}.

    – ferhan

    20. April 2012 um 19:11 Uhr

  • Das liegt daran, dass Ihre Klasse keine hat .__init__() Methodenfunktion, sodass Ihre Klasseninstanz ein leeres Wörterbuch hat. Mit anderen Worten, {} ist das richtige Ergebnis für Ihren Beispielcode.

    – steveha

    20. April 2012 um 19:37 Uhr

  • Danke. Das macht den Trick. Ich habe eine einfache hinzugefügt drin ohne Parameter und jetzt das Aufrufen von json.dumps(t.__dict__) gibt korrekte Daten im Format von: {“value2”: “345”, “value1”: “123”} zurück. Ich hatte solche Posts schon einmal gesehen, war’ Ich bin mir nicht sicher, ob ich einen benutzerdefinierten Serialisierer für Mitglieder benötigte drin wurde nicht explizit erwähnt oder ich habe es übersehen. Danke.

    – ferhan

    20. April 2012 um 19:56 Uhr


  • Dies funktioniert für eine einzelne Klasse, aber nicht für Objekte verwandter Klassen

    – Nwawel A Iroume

    18. Dezember 2015 um 14:54 Uhr

  • @NwawelAIroume: Stimmt. Wenn Sie ein Objekt haben, das zB mehrere Objekte in einer Liste enthält, bleibt der Fehler bestehen is not JSON serializable

    – gies0r

    5. März 2017 um 17:47 Uhr

Es gibt einen Weg, der für mich großartig funktioniert und den Sie ausprobieren können:

json.dumps() kann einen optionalen Parameter annehmen Ursprünglich wo Sie eine benutzerdefinierte Serializer-Funktion für unbekannte Typen angeben können, die in meinem Fall so aussieht

def serialize(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

Die ersten beiden ifs sind für die Serialisierung von Datum und Uhrzeit und dann gibt es noch eine obj.__dict__ für jedes andere Objekt zurückgegeben.

Der letzte Aufruf sieht so aus:

json.dumps(myObj, default=serialize)

Es ist besonders gut, wenn Sie eine Sammlung serialisieren und nicht anrufen möchten __dict__ explizit für jedes Objekt. Hier wird es automatisch für Sie erledigt.

Hat bisher so gut für mich funktioniert, ich freue mich auf Ihre Gedanken.

  • Ich bekomme NameError: name 'serialize' is not defined. Irgendwelche Tipps?

    – Kyle Delaney

    10. April 2019 um 16:50 Uhr

  • Sehr schön. Nur für Klassen mit Slots: try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict

    – Fantasiegeschichte

    28. Juni 2019 um 9:20 Uhr


Benutzer-Avatar
codeman48

Sie können die angeben default benannter Parameter in der json.dumps() Funktion:

json.dumps(obj, default=lambda x: x.__dict__)

Erläuterung:

Bilden Sie die Dokumente (2.7, 3.6):

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(Funktioniert auf Python 2.7 und Python 3.x)

Hinweis: In diesem Fall benötigen Sie instance Variablen und nicht class Variablen, wie es das Beispiel in der Frage versucht. (Ich nehme an, der Fragesteller meinte class instance Objekt einer Klasse sein)

Ich habe das zuerst aus der Antwort von @phihag hier gelernt. Fand es die einfachste und sauberste Art, die Arbeit zu erledigen.

  • Das hat bei mir funktioniert, aber wegen datetime.date-Mitgliedern habe ich es leicht geändert: default=lambda x: getattr(x, '__dict__', str(x))

    – Dakota Hawkins

    20. November 2018 um 5:30 Uhr

  • @Dakota nette Problemumgehung; datetime.date ist eine C-Implementierung, daher hat es keine __dict__ Attribut. IMHO der Einheitlichkeit halber, datetime.date sollte man haben…

    – codeman48

    20. November 2018 um 10:32 Uhr

Benutzer-Avatar
gies0r

Verwenden jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)

Ich mache einfach:

data=json.dumps(myobject.__dict__)

Dies ist nicht die vollständige Antwort, und wenn Sie eine komplizierte Objektklasse haben, werden Sie sicherlich nicht alles bekommen. Ich verwende dies jedoch für einige meiner einfachen Objekte.

Eine, mit der es wirklich gut funktioniert, ist die Klasse “Optionen”, die Sie vom OptionParser-Modul erhalten. Hier ist es zusammen mit der JSON-Anforderung selbst.

  def executeJson(self, url, options):
        data=json.dumps(options.__dict__)
        if options.verbose:
            print data
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        return requests.post(url, data, headers=headers)

  • Möglicherweise möchten Sie self entfernen, wenn Sie dies nicht innerhalb einer Klasse verwenden.

    – Spirail

    10. Juli 2013 um 14:37 Uhr

  • Das funktioniert gut, solange das Objekt nicht aus anderen Objekten zusammengesetzt ist.

    – Haroldo_OK

    27. Mai 2014 um 11:56 Uhr

Python3.x

Der beste Ansatz, den ich mit meinem Wissen erreichen konnte, war dieser.
Beachten Sie, dass dieser Code auch set() behandelt.
Dieser Ansatz ist generisch und benötigt lediglich die Erweiterung der Klasse (im zweiten Beispiel).
Beachten Sie, dass ich es nur mit Dateien mache, aber es ist einfach, das Verhalten nach Ihrem Geschmack zu ändern.

Dies ist jedoch ein CoDec.

Mit etwas mehr Arbeit können Sie Ihre Klasse auch auf andere Weise konstruieren. Ich gehe davon aus, dass ein Standardkonstruktor es instanziiert, und aktualisiere dann die Klasse dict.

import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

Bearbeiten

Mit etwas mehr Recherche fand ich einen Weg, ohne die Notwendigkeit des zu verallgemeinern SUPERKLASSE Methodenaufruf registrieren, mit a Metaklasse

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s

  • Möglicherweise möchten Sie self entfernen, wenn Sie dies nicht innerhalb einer Klasse verwenden.

    – Spirail

    10. Juli 2013 um 14:37 Uhr

  • Das funktioniert gut, solange das Objekt nicht aus anderen Objekten zusammengesetzt ist.

    – Haroldo_OK

    27. Mai 2014 um 11:56 Uhr

Benutzer-Avatar
Brendan Holz

JSON ist nicht wirklich dafür gedacht, beliebige Python-Objekte zu serialisieren. Es eignet sich hervorragend zum Serialisieren dict Objekte, aber die pickle Modul ist wirklich das, was Sie im Allgemeinen verwenden sollten. Ausgabe von pickle ist nicht wirklich menschenlesbar, aber es sollte gut entpicken. Wenn Sie darauf bestehen, JSON zu verwenden, können Sie sich die jsonpickle Modul, was ein interessanter hybrider Ansatz ist.

https://github.com/jsonpickle/jsonpickle

  • Das Hauptproblem, das ich bei Pickle sehe, ist, dass es sich um ein Python-spezifisches Format handelt, während JSON ein plattformunabhängiges Format ist. JSON ist besonders nützlich, wenn Sie entweder eine Webanwendung oder ein Backend für eine mobile Anwendung schreiben. Trotzdem danke, dass Sie auf jsonpickle hingewiesen haben.

    – Haroldo_OK

    27. Mai 2014 um 11:50 Uhr


  • @Haroldo_OK Exportiert jsonpickle nicht immer noch nach JSON, nur nicht sehr menschenlesbar?

    – Cälum

    24. November 2015 um 10:51 Uhr

  • Die Frage bezieht sich nicht auf die Serialisierung, sondern auf die Serialisierung in JSON. Es gibt viele Gründe dafür, und Pickle wäre für die meisten von ihnen ein schreckliches Werkzeug.

    – Tarynn

    21. Januar um 21:02 Uhr

1005530cookie-checkKlasseninstanz in JSON serialisieren

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy