Serialisierung der Klasseninstanz an JSON

185

Ich versuche, eine JSON-Zeichenfolgendarstellung einer Klasseninstanz zu erstellen, und habe Schwierigkeiten. Angenommen, die Klasse ist folgendermaßen aufgebaut:

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

Ein Aufruf von json.dumps erfolgt wie folgt:

t = testclass()
json.dumps(t)

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 Gurkenmodul 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?

Ferhan
quelle
30
Verwenden Sie eine Linie, s = json.dumps(obj, default=lambda x: x.__dict__), serialisiert Objektinstanzvariablen ( self.value1, self.value2, ...). Es ist der einfachste und direkteste Weg. Verschachtelte Objektstrukturen werden serialisiert. Die defaultFunktion 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, was wahrscheinlich schon vor langer Zeit zutraf.
Codeman48
1
Sie testclasshaben keine __init__()Methode, daher teilen alle Instanzen dieselben zwei Klassenattribute ( value1und value2), die in der Klassenanweisung definiert sind. Verstehst du den Unterschied zwischen einer Klasse und einer Instanz von einer?
Martineau
1
Es gibt eine Python-Bibliothek für diese github.com/jsonpickle/jsonpickle (Kommentar, da die Antwort zu unten im Thread ist und nicht erreichbar ist.)
Beste Wünsche

Antworten:

237

Das Grundproblem besteht darin, dass der JSON-Encoder json.dumps()nur eine begrenzte Anzahl von Objekttypen serialisieren kann, standardmäßig 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 von JSONEncoderder JSONEncoder.default()Funktion erben zu lassen und diese dann zu implementieren und diese Funktion den richtigen JSON für Ihre Klasse ausgeben zu lassen.

Eine einfache Lösung wäre, json.dumps()das .__dict__Mitglied dieser Instanz aufzurufen . Das ist ein Standard-Python dictund 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 Blogeintrag erläutert:

    Serialisierung beliebiger Python-Objekte in JSON mithilfe von __dict__

steveha
quelle
3
Ich habe es versucht. Das Endergebnis eines Aufrufs von json.dumps (t .__ dict__) ist nur {}.
Ferhan
6
Das liegt daran, dass Ihre Klasse keine .__init__()Methodenfunktion hat, sodass Ihre Klasseninstanz ein leeres Wörterbuch hat. Mit anderen Worten, {}ist das richtige Ergebnis für Ihren Beispielcode.
Steveha
3
Vielen Dank. Das macht den Trick. Ich habe eine einfache Init ohne Parameter hinzugefügt und jetzt rufe json.dumps (t .__ dict__) die richtigen Daten im Format {{value2 ":" 345 "," value1 ":" 123 "} auf. Ich hatte Beiträge wie gesehen Ich war mir vorher nicht sicher, ob ich einen benutzerdefinierten Serializer für Mitglieder benötigte. Init zu benötigen, wurde nicht explizit erwähnt oder ich habe es verpasst. Danke dir.
Ferhan
3
Diese Arbeit für eine einzelne Klasse, aber nicht mit verwandten
Klassenobjekten
2
@NwawelAIroume: Stimmt. Wenn Sie ein Objekt haben, das z. B. mehrere Objekte in einer Liste enthält, ist der Fehler immer nochis not JSON serializable
gies0r
57

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

json.dumps()kann einen optionalen Parameter als Standard verwenden, in dem 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 beziehen sich auf die Serialisierung von Datum und Uhrzeit, und dann wird obj.__dict__für jedes andere Objekt eine Rückgabe zurückgegeben.

Der letzte Anruf sieht aus wie:

json.dumps(myObj, default=serialize)

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

Bisher hat es so gut für mich funktioniert und ich freue mich auf deine Gedanken.

Brokkoli
quelle
Ich verstehe NameError: name 'serialize' is not defined. Irgendwelche Tipps?
Kyle Delaney
Sehr schön. Nur für Klassen, die Slots haben:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
fantastisch
Es ist erstaunlich, dass eine so beliebte Sprache niemanden hat, der ein Objekt jsoniniert. Muss sein, weil es nicht statisch typisiert ist.
TheRennen
48

Sie können den defaultbenannten Parameter in der json.dumps()Funktion angeben :

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 mit Python 2.7 und Python 3.x)

Hinweis: In diesem Fall benötigen Sie instanceVariablen und keine classVariablen, wie im Beispiel in der Frage versucht. (Ich gehe davon aus, dass der Fragesteller class instanceein Objekt einer Klasse sein soll)

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

codeman48
quelle
6
Dies funktionierte für mich, aber aufgrund von datetime.date-Mitgliedern habe ich es leicht geändert:default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins
@ Dakota schöne Abhilfe; datetime.dateist eine C-Implementierung, daher hat sie kein __dict__Attribut. IMHO aus Gründen der Einheitlichkeit datetime.datesollte es sein ...
codeman48
22

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 benutze dies jedoch für einige meiner einfachen Objekte.

Eine, bei der es sehr gut funktioniert, ist die Klasse "options", die Sie vom OptionParser-Modul erhalten. Hier ist es zusammen mit der JSON-Anfrage 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)
SpiRail
quelle
Möglicherweise möchten Sie self entfernen, wenn Sie dies nicht in einer Klasse verwenden.
SpiRail
3
Das wird in Ordnung funktionieren, solange das Objekt nicht aus anderen Objekten besteht.
Haroldo_OK
19

Mit jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)
gies0r
quelle
5

JSON ist nicht wirklich zum Serialisieren beliebiger Python-Objekte gedacht. Es eignet sich hervorragend zum Serialisieren von dictObjekten, aber das pickleModul ist genau das, was Sie im Allgemeinen verwenden sollten. Die Ausgabe von pickleist nicht wirklich lesbar, sollte sich aber problemlos lösen. Wenn Sie darauf bestehen, JSON zu verwenden, können Sie sich das jsonpickleModul ansehen, das ein interessanter hybrider Ansatz ist.

https://github.com/jsonpickle/jsonpickle

Brendan Wood
quelle
9
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. Nachdem dies gesagt wurde, danke, dass Sie jsonpickle darauf hingewiesen haben.
Haroldo_OK
@Haroldo_OK Exportiert jsonpickle nicht immer noch nach JSON, nur nicht sehr gut lesbar?
Caelum
4

Hier sind zwei einfache Funktionen für die Serialisierung von nicht hoch entwickelten Klassen, nichts Besonderes, wie zuvor erläutert.

Ich verwende dies für Konfigurationsarten, da ich den Klassen ohne Code-Anpassungen neue Mitglieder hinzufügen kann.

import json

class SimpleClass:
    def __init__(self, a=None, b=None, c=None):
        self.a = a
        self.b = b
        self.c = c

def serialize_json(instance=None, path=None):
    dt = {}
    dt.update(vars(instance))

    with open(path, "w") as file:
        json.dump(dt, file)

def deserialize_json(cls=None, path=None):
    def read_json(_path):
        with open(_path, "r") as file:
            return json.load(file)

    data = read_json(path)

    instance = object.__new__(cls)

    for key, value in data.items():
        setattr(instance, key, value)

    return instance

# Usage: Create class and serialize under Windows file system.
write_settings = SimpleClass(a=1, b=2, c=3)
serialize_json(write_settings, r"c:\temp\test.json")

# Read back and rehydrate.
read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")

# results are the same.
print(vars(write_settings))
print(vars(read_settings))

# output:
# {'c': 3, 'b': 2, 'a': 1}
# {'c': 3, 'b': 2, 'a': 1}
GBGOLC
quelle
3

Es gibt einige gute Antworten, wie Sie damit beginnen können. Es gibt jedoch einige Dinge zu beachten:

  • Was ist, wenn die Instanz in einer großen Datenstruktur verschachtelt ist?
  • Was ist, wenn Sie auch den Klassennamen möchten?
  • Was ist, wenn Sie die Instanz deserialisieren möchten?
  • Was ist, wenn Sie __slots__anstelle von verwenden __dict__?
  • Was ist, wenn Sie es einfach nicht selbst tun wollen?

json-Tricks ist eine Bibliothek (die ich erstellt habe und zu der andere beigetragen haben), die dies schon seit einiger Zeit kann. Beispielsweise:

class MyTestCls:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

cls_instance = MyTestCls(s='ub', dct={'7': 7})

json = dumps(cls_instance, indent=4)
instance = loads(json)

Sie erhalten Ihre Instanz zurück. Hier sieht der json so aus:

{
    "__instance_type__": [
        "json_tricks.test_class",
        "MyTestCls"
    ],
    "attributes": {
        "s": "ub",
        "dct": {
            "7": 7
        }
    }
}

Wenn Sie Ihre eigene Lösung erstellen möchten, können Sie sich die Quelle ansehen json-tricks, um einige Sonderfälle (wie __slots__) nicht zu vergessen .

Es werden auch andere Typen wie Numpy-Arrays, Datumsangaben und komplexe Zahlen ausgeführt. es erlaubt auch Kommentare.

Kennzeichen
quelle
3

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 nur 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 auf andere Weise aufbauen. Ich gehe davon aus, dass ein Standardkonstruktor es instanziiert, und aktualisiere dann das Klassendiktat.

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 habe ich einen Weg gefunden, ohne die Notwendigkeit des Aufrufs der SUPERCLASS- Registermethode mithilfe einer Metaklasse zu verallgemeinern

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
Davi Abreu Wasserberg
quelle
2

Ich glaube, anstelle der Vererbung, wie in der akzeptierten Antwort vorgeschlagen, ist es besser, Polymorphismus zu verwenden. Andernfalls müssen Sie eine große if else-Anweisung haben, um die Codierung jedes Objekts anzupassen. Das bedeutet, dass Sie einen generischen Standardcodierer für JSON erstellen als:

def jsonDefEncoder(obj):
   if hasattr(obj, 'jsonEnc'):
      return obj.jsonEnc()
   else: #some default behavior
      return obj.__dict__

und haben dann eine jsonEnc()Funktion in jeder Klasse, die Sie serialisieren möchten. z.B

class A(object):
   def __init__(self,lengthInFeet):
      self.lengthInFeet=lengthInFeet
   def jsonEnc(self):
      return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter

Dann rufst du an json.dumps(classInstance,default=jsonDefEncoder)

hwat
quelle