So machen Sie eine Klasse JSON serialisierbar

834

Wie mache ich eine Python-Klasse serialisierbar?

Eine einfache Klasse:

class FileItem:
    def __init__(self, fname):
        self.fname = fname

Was soll ich tun, um eine Ausgabe zu erhalten:

>>> import json

>>> my_file = FileItem('/foo/bar')
>>> json.dumps(my_file)
TypeError: Object of type 'FileItem' is not JSON serializable

Ohne den Fehler

Sergey
quelle
31
Es ist bedauerlich, dass alle Antworten die Frage "Wie serialisiere ich eine Klasse?" Zu beantworten scheinen. anstatt der Aktionsfrage "Wie mache ich eine Klasse serialisierbar?" Bei diesen Antworten wird davon ausgegangen, dass Sie die Serialisierung selbst durchführen, anstatt das Objekt an ein anderes Modul weiterzugeben, das es serialisiert.
Kyle Delaney
Wenn Sie Python3.5 + verwenden, können Sie jsons verwenden. Es konvertiert Ihr Objekt (und alle seine Attribute rekursiv ) in ein Diktat. import jsonssiehe Antwort unten - es funktioniert einwandfrei
Tswaehn

Antworten:

551

Haben Sie eine Vorstellung von der erwarteten Leistung? Zum Beispiel reicht das?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

In diesem Fall können Sie lediglich anrufen json.dumps(f.__dict__).

Wenn Sie eine individuellere Ausgabe wünschen, müssen Sie eine Unterklasse erstellen JSONEncoderund Ihre eigene benutzerdefinierte Serialisierung implementieren.

Ein einfaches Beispiel finden Sie unten.

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

Dann übergeben Sie diese Klasse json.dumps()als clskwarg an die Methode :

json.dumps(cls=MyEncoder)

Wenn Sie auch dekodieren möchten, müssen Sie object_hookder JSONDecoderKlasse eine benutzerdefinierte Datei bereitstellen . Zum Beispiel

>>> def from_json(json_object):
        if 'fname' in json_object:
            return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>> 
Manoj Govindan
quelle
44
Die Verwendung __dict__funktioniert nicht in allen Fällen. Wenn die Attribute nach der Instanziierung des Objekts nicht festgelegt wurden, werden sie __dict__möglicherweise nicht vollständig ausgefüllt. Im obigen Beispiel sind Sie in Ordnung. Wenn Sie jedoch Klassenattribute haben, die Sie ebenfalls codieren möchten, werden diese nicht aufgelistet, es __dict__sei denn, sie wurden im __init__Aufruf der Klasse oder auf andere Weise geändert , nachdem das Objekt instanziiert wurde.
Kris Hardy
8
+1, aber die from_json()als Objekt-Hook verwendete Funktion sollte eine else: return json_objectAnweisung haben, damit sie auch allgemeine Objekte behandeln kann.
Jogojapan
8
@KrisHardy __dict__funktioniert auch nicht, wenn Sie __slots__eine neue Stilklasse verwenden .
Badp
7
Sie können JSONEncoderein benutzerdefiniertes Protokoll wie oben verwenden, um ein benutzerdefiniertes Protokoll zu erstellen, z. B. das Vorhandensein einer __json_serializable__Methode zu überprüfen und es aufzurufen, um eine serialisierbare JSON-Darstellung des Objekts zu erhalten. Dies würde mit anderem Python - Muster im Einklang sein, wie __getitem__, __str__, __eq__, und __len__.
jpmc26
5
__dict__funktioniert auch nicht rekursiv, z. B. wenn ein Attribut Ihres Objekts ein anderes Objekt ist.
Neel
634

Hier ist eine einfache Lösung für eine einfache Funktion:

.toJSON() Methode

Implementieren Sie anstelle einer serialisierbaren JSON-Klasse eine Serialisierungsmethode:

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

Sie rufen es einfach auf, um zu serialisieren:

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

wird ausgegeben:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}
Onur Yıldırım
quelle
82
Sehr begrenzt. Wenn Sie ein Diktat {"foo": "bar", "baz": "bat"} haben, kann dieses problemlos in JSON serialisiert werden. Wenn Sie stattdessen {"foo": "bar", "baz": MyObject ()} haben, können Sie dies nicht. Die ideale Situation wäre, dass verschachtelte Objekte rekursiv und nicht explizit in JSON serialisiert werden.
Mark E. Haase
30
Es wird immer noch funktionieren. Du vermisst o.__dict___. Versuchen Sie Ihr eigenes Beispiel: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
Onur Yıldırım
14
Ist diese Lösung reversibel? Dh ist es einfach, das Objekt aus json zu rekonstruieren?
Jorge Leitao
2
@ JCLeitão Nein. Sie können zwei verschiedene Klassen mit denselben Feldern haben. Objekte a und b dieser Klasse (wahrscheinlich mit denselben Eigenschaften) hätten dasselbe a.__dict__/ b.__dict__.
Martin Thoma
7
Dies funktioniert nicht mit datetime.datetimeInstanzen. Es wirft den folgenden Fehler aus:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger
171

Für komplexere Klassen können Sie das Tool jsonpickle in Betracht ziehen :

jsonpickle ist eine Python-Bibliothek zur Serialisierung und Deserialisierung komplexer Python-Objekte von und nach JSON.

Die Standard-Python-Bibliotheken zum Codieren von Python in JSON, wie z. B. json, simplejson und demjson der stdlib, können nur Python-Grundelemente verarbeiten, die ein direktes JSON-Äquivalent haben (z. B. Dikte, Listen, Zeichenfolgen, Ints usw.). jsonpickle baut auf diesen Bibliotheken auf und ermöglicht die Serialisierung komplexerer Datenstrukturen in JSON. jsonpickle ist hochgradig konfigurierbar und erweiterbar, sodass der Benutzer das JSON-Backend auswählen und zusätzliche Backends hinzufügen kann.

(Link zu jsonpickle auf PyPi)

Gecco
quelle
32
Von C # kommend, hatte ich das erwartet. Ein einfacher Einzeiler und kein Durcheinander mit den Klassen.
Jerther
2
jsonpickle ist großartig. Es funktionierte perfekt für ein riesiges, komplexes, chaotisches Objekt mit vielen Klassenstufen
wisbucky
Gibt es ein Beispiel für den richtigen Weg, dies in einer Datei zu speichern? Die Dokumentation zeigt nur, wie ein jsonpickleObjekt codiert und decodiert wird. Dies war auch nicht in der Lage, ein Diktat von Diktaten zu entschlüsseln, die Pandas-Datenrahmen enthielten.
user5359531
3
@ user5359531 können Sie verwenden obj = jsonpickle.decode(file.read())und file.write(jsonpickle.encode(obj)).
Kilian Batzner
1
Eine Frage speziell für Django: Hat die Verwendung von jsonpickle zur Serialisierung von Sitzungsdaten dieselbe Sicherheitsanfälligkeit wie pickle? (wie hier beschrieben docs.djangoproject.com/de/1.11/topics/http/sessions/… )?
Paul Bormans
89

Die meisten Antworten beinhalten das Ändern des Aufrufs in json.dumps () , was nicht immer möglich oder wünschenswert ist (dies kann beispielsweise innerhalb einer Framework-Komponente geschehen).

Wenn Sie json.dumps (obj) so wie es ist aufrufen möchten, erbt eine einfache Lösung von dict :

class FileItem(dict):
    def __init__(self, fname):
        dict.__init__(self, fname=fname)

f = FileItem('tasks.txt')
json.dumps(f)  #No need to change anything here

Dies funktioniert, wenn Ihre Klasse nur eine grundlegende Datendarstellung ist. Für schwierigere Dinge können Sie Schlüssel immer explizit festlegen.

andyhasit
quelle
2
Dies kann wirklich eine schöne Lösung sein :) Ich glaube für meinen Fall ist es. Vorteile: Sie kommunizieren die "Form" des Objekts, indem Sie es mit init zu einer Klasse machen. Es ist von Natur aus serialisierbar und sieht als repr interpretierbar aus .
PascalVKooten
1
Obwohl "
Punktzugriff
2
Ahh das scheint zu funktionieren! Danke, nicht sicher, warum dies nicht die akzeptierte Antwort ist. Ich stimme voll und ganz zu, dass das Ändern der dumpskeine gute Lösung ist. Übrigens, in den meisten Fällen möchten Sie wahrscheinlich eine dictVererbung zusammen mit einer Delegierung haben, was bedeutet, dass Sie ein dictTypattribut in Ihrer Klasse haben. Anschließend übergeben Sie dieses Attribut als Parameter als Initialisierung super().__init__(self.elements).
Cglacet
47

Ich mag Onurs Antwort , würde sie aber um eine optionale toJSON()Methode erweitern, mit der Objekte sich selbst serialisieren können:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Jason S.
quelle
Ich fand, dass dies die beste Balance zwischen der Verwendung des vorhandenen json.dumpsund der Einführung einer benutzerdefinierten Handhabung ist. Vielen Dank!
Daniel Buckmaster
12
Ich mag das wirklich sehr; aber anstatt try-catchwahrscheinlich etwas zu tun wie if 'toJSON' in obj.__attrs__():... um einen stillen Fehler zu vermeiden (im Falle eines Fehlers in toJSON () aus einem anderen Grund als wenn es nicht vorhanden ist) ... einen Fehler, der möglicherweise zu einer Beschädigung der Daten führt.
Der
39

Eine andere Option besteht darin, JSON-Dumping in eine eigene Klasse zu packen:

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def __repr__(self):
        return json.dumps(self.__dict__)

Oder, noch besser, die FileItem-Klasse einer JsonSerializableKlasse unterzuordnen :

import json

class JsonSerializable(object):
    def toJson(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.toJson()


class FileItem(JsonSerializable):
    def __init__(self, fname):
        self.fname = fname

Testen:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'
Paulo Freitas
quelle
2
Hallo, ich mag diesen "benutzerdefinierten Encoder" -Ansatz nicht wirklich. Es wäre besser, wenn Sie Ihre Klasse json seriazable machen könnten. Ich versuche und versuche und versuche und nichts. Gibt es eine Idee, wie das geht? Die Sache ist, dass das JSON-Modul Ihre Klasse gegen eingebaute Python-Typen testet und sogar sagt, dass benutzerdefinierte Klassen Ihren Encoder machen :). Kann es gefälscht werden? Also könnte ich meiner Klasse etwas antun, damit sie sich wie eine einfache Liste für das JSON-Modul verhält? Ich versuche Subclasscheck und Instancecheck aber nichts.
Bojan Radojevic
@ADRENALIN Sie könnten von einem Primärtyp erben (wahrscheinlich diktieren), wenn alle Klassenattributwerte serialisierbar sind und Sie nichts gegen Hacks haben. Sie können auch jsonpickle oder json_tricks oder etwas anderes anstelle des Standardcodierers verwenden (immer noch ein benutzerdefinierter Encoder, aber keinen, den Sie schreiben oder aufrufen müssen). Ersteres wählt die Instanz aus, letzteres speichert sie als Diktat von Attributen, die Sie durch Implementieren von __json__encode__/ ändern können __json_decode__(Offenlegung: Ich habe die letzte erstellt).
Mark
30

Fügen Sie to_jsonIhrer Klasse einfach folgende Methode hinzu :

def to_json(self):
  return self.message # or how you want it to be serialized

Und fügen Sie diesen Code (aus dieser Antwort ) irgendwo oben hinzu:

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

Dadurch wird das JSON-Modul beim Import mit einem Affen-Patch versehen, sodass JSONEncoder.default () automatisch nach einer speziellen "to_json ()" - Methode sucht und diese zum Codieren des Objekts verwendet, wenn es gefunden wird.

Genau wie Onur sagte, aber dieses Mal müssen Sie nicht alle json.dumps()in Ihrem Projekt aktualisieren .

Lust auf John
quelle
6
Vielen Dank! Dies ist die einzige Antwort, mit der ich tun kann, was ich will: ein Objekt serialisieren können, ohne den vorhandenen Code zu ändern. Die anderen Methoden funktionieren bei mir meistens nicht. Das Objekt wird in einer Bibliothek eines Drittanbieters definiert, und der Serialisierungscode stammt ebenfalls von einem Drittanbieter. Das Ändern wird umständlich sein. Mit Ihrer Methode muss ich nur tun TheObject.to_json = my_serializer.
Yongwei Wu
24

Ich bin neulich auf dieses Problem gestoßen und habe eine allgemeinere Version eines Encoders für Python-Objekte implementiert, der verschachtelte Objekte und geerbte Felder verarbeiten kann :

import json
import inspect

class ObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "to_json"):
            return self.default(obj.to_json())
        elif hasattr(obj, "__dict__"):
            d = dict(
                (key, value)
                for key, value in inspect.getmembers(obj)
                if not key.startswith("__")
                and not inspect.isabstract(value)
                and not inspect.isbuiltin(value)
                and not inspect.isfunction(value)
                and not inspect.isgenerator(value)
                and not inspect.isgeneratorfunction(value)
                and not inspect.ismethod(value)
                and not inspect.ismethoddescriptor(value)
                and not inspect.isroutine(value)
            )
            return self.default(d)
        return obj

Beispiel:

class C(object):
    c = "NO"
    def to_json(self):
        return {"c": "YES"}

class B(object):
    b = "B"
    i = "I"
    def __init__(self, y):
        self.y = y

    def f(self):
        print "f"

class A(B):
    a = "A"
    def __init__(self):
        self.b = [{"ab": B("y")}]
        self.c = C()

print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)

Ergebnis:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}
tobigue
quelle
1
Obwohl dies ein bisschen alt ist ... Ich habe einige zirkuläre Importfehler. Also anstatt return objin der letzten Zeile habe ich das getan return super(ObjectEncoder, self).default(obj). Referenz HIER
SomeTypeFoo
24

Wenn Sie Python3.5 + verwenden, können Sie verwenden jsons. Es konvertiert Ihr Objekt (und alle seine Attribute rekursiv) in ein Diktat.

import jsons

a_dict = jsons.dump(your_object)

Oder wenn Sie eine Zeichenfolge wollten:

a_str = jsons.dumps(your_object)

Oder wenn Ihre Klasse implementiert jsons.JsonSerializable:

a_dict = your_object.json
RH
quelle
3
Wenn Sie Python 3.7+ verwenden können, habe ich festgestellt, dass die sauberste Lösung zum Konvertieren von Python-Klassen in Dicts und JSON-Zeichenfolgen (und umgekehrt) darin besteht, die jsonsBibliothek mit Datenklassen zu mischen . So weit, so gut für mich!
Ruluk
3
Dies ist eine externe Bibliothek, die nicht in die Standardinstallation von Python integriert ist.
Noumenon
Nur für Klassen mit Slots- Attributen
Yehudahs
Sie können, aber Sie müssen keine Slots verwenden . Nur wenn Sie gemäß der Signatur einer bestimmten Klasse ausgeben, benötigen Sie Slots . In der kommenden Version 1.1.0 ist dies ebenfalls nicht mehr der Fall.
RH
11
import simplejson

class User(object):
    def __init__(self, name, mail):
        self.name = name
        self.mail = mail

    def _asdict(self):
        return self.__dict__

print(simplejson.dumps(User('alice', '[email protected]')))

Wenn Sie Standard verwenden json, müssen Sie eine defaultFunktion definieren

import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', '[email protected]'), default=default))
tryer3000
quelle
2
Ich habe dies vereinfacht, indem ich die Funktion _asdict mit einem Lambda entfernt habe json.dumps(User('alice', '[email protected]'), default=lambda x: x.__dict__)
JustEngland
8

jsonist in Bezug auf Objekte, die gedruckt werden können, begrenzt, und jsonpickle(möglicherweise benötigen Sie a pip install jsonpickle) ist in Bezug auf Objekte, die keinen Text einrücken können, begrenzt. Wenn Sie den Inhalt eines Objekts untersuchen möchten, dessen Klasse Sie nicht ändern können, konnte ich immer noch keinen besseren Weg finden als:

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

Hinweis: Die Objektmethoden können immer noch nicht gedruckt werden.

Ribamar
quelle
6

Diese Klasse kann den Trick machen, sie konvertiert das Objekt in Standard-JSON.

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

Verwendungszweck:

Serializer.serialize(my_object)

Arbeiten in python2.7und python3.

Koder verloren
quelle
Diese Methode hat mir am besten gefallen. Beim Versuch, komplexere Objekte zu serialisieren, deren Mitglieder / Methoden nicht serialisierbar sind, sind Probleme aufgetreten. Hier ist meine Implementierung, die für mehr Objekte funktioniert: `` `class Serializer (Objekt): @staticmethod def serialize (obj): def check (o): für k, v in o .__ dict __. Items (): try: _ = json .dumps (v) o .__ dict __ [k] = v außer TypeError: o .__ dict __ [k] = str (v) return o return json.dumps (check (obj) .__ dict__, indent = 2) `` `
Will Charlton
4
import json

class Foo(object):
    def __init__(self):
        self.bar = 'baz'
        self._qux = 'flub'

    def somemethod(self):
        pass

def default(instance):
    return {k: v
            for k, v in vars(instance).items()
            if not str(k).startswith('_')}

json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo

print(json_foo)
Rechteck
quelle
Aus doc : Der Parameter default(obj)ist eine Funktion, die eine serialisierbare Version von obj zurückgeben oder TypeError auslösen soll. Die Standardeinstellung defaultlöst einfach TypeError aus.
Luckydonald
4

jaraco gab eine ziemlich ordentliche Antwort. Ich musste einige kleinere Dinge reparieren, aber das funktioniert:

Code

# Your custom class
class MyCustom(object):
    def __json__(self):
        return {
            'a': self.a,
            'b': self.b,
            '__python__': 'mymodule.submodule:MyCustom.from_json',
        }

    to_json = __json__  # supported by simplejson

    @classmethod
    def from_json(cls, json):
        obj = cls()
        obj.a = json['a']
        obj.b = json['b']
        return obj

# Dumping and loading
import simplejson

obj = MyCustom()
obj.a = 3
obj.b = 4

json = simplejson.dumps(obj, for_json=True)

# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)

# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__

Beachten Sie, dass wir zum Laden zwei Schritte benötigen. Derzeit wird die __python__Eigenschaft nicht verwendet.

Wie häufig ist das?

Mit der Methode von AlJohri überprüfe ich die Popularität von Ansätzen:

Serialisierung (Python -> JSON):

Deserialisierung (JSON -> Python):

Martin Thoma
quelle
4

Das hat bei mir gut funktioniert:

class JsonSerializable(object):

    def serialize(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.serialize()

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

und dann

class FileItem(JsonSerializable):
    ...

und

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
jmhostalet
quelle
3

Wenn es Ihnen nichts ausmacht, ein Paket dafür zu installieren, können Sie json-Tricks verwenden :

pip install json-tricks

Danach müssen Sie nur noch dump(s)von json_tricksstatt json importieren , und es funktioniert normalerweise:

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

was geben wird

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

Und das war's auch schon!


Dies wird im Allgemeinen gut funktionieren. Es gibt einige Ausnahmen, z. B. wenn besondere Dinge passieren __new__oder mehr Metaklassenmagie stattfindet.

Natürlich funktioniert das Laden auch (ansonsten, worum geht es):

from json_tricks import loads
json_str = loads(json_str)

Dies setzt voraus, dass module_name.test_class.MyTestClses importiert werden kann und sich nicht auf nicht kompatible Weise geändert hat. Sie erhalten eine Instanz zurück , kein Wörterbuch oder ähnliches, und es sollte eine identische Kopie sein wie die, die Sie ausgegeben haben.

Wenn Sie anpassen möchten, wie etwas (de) serialisiert wird, können Sie Ihrer Klasse spezielle Methoden hinzufügen, z.

class CustomEncodeCls:
        def __init__(self):
                self.relevant = 42
                self.irrelevant = 37

        def __json_encode__(self):
                # should return primitive, serializable types like dict, list, int, string, float...
                return {'relevant': self.relevant}

        def __json_decode__(self, **attrs):
                # should initialize all properties; note that __init__ is not called implicitly
                self.relevant = attrs['relevant']
                self.irrelevant = 12

Dies serialisiert beispielsweise nur einen Teil der Attributparameter.

Als kostenlosen Bonus erhalten Sie eine (De-) Serialisierung von Numpy-Arrays, Datum und Uhrzeit, geordneten Karten sowie die Möglichkeit, Kommentare in json aufzunehmen.

Haftungsausschluss: Ich habe json_tricks erstellt , weil ich das gleiche Problem hatte wie Sie.

Kennzeichen
quelle
1
Ich habe gerade json_tricks getestet und es hat verschönert (im Jahr 2019).
Pauljohn32
2

jsonweb scheint die beste Lösung für mich zu sein. Siehe http://www.jsonweb.info/en/latest/

from jsonweb.encode import to_object, dumper

@to_object()
class DataModel(object):
  def __init__(self, id, value):
   self.id = id
   self.value = value

>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'
matthewlent
quelle
Funktioniert es gut für verschachtelte Objekte? Einschließlich Dekodierung und Kodierung
Simone Zandara
1

Hier sind meine 3 Cent ...
Dies demonstriert die explizite JSON-Serialisierung für ein baumartiges Python-Objekt.
Hinweis: Wenn Sie tatsächlich einen solchen Code wünschen, können Sie die verdrehte FilePath- Klasse verwenden.

import json, sys, os

class File:
    def __init__(self, path):
        self.path = path

    def isdir(self):
        return os.path.isdir(self.path)

    def isfile(self):
        return os.path.isfile(self.path)

    def children(self):        
        return [File(os.path.join(self.path, f)) 
                for f in os.listdir(self.path)]

    def getsize(self):        
        return os.path.getsize(self.path)

    def getModificationTime(self):
        return os.path.getmtime(self.path)

def _default(o):
    d = {}
    d['path'] = o.path
    d['isFile'] = o.isfile()
    d['isDir'] = o.isdir()
    d['mtime'] = int(o.getModificationTime())
    d['size'] = o.getsize() if o.isfile() else 0
    if o.isdir(): d['children'] = o.children()
    return d

folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)
Dan Brough
quelle
1

Ich bin auf dieses Problem gestoßen, als ich versucht habe, Peewees Modell in PostgreSQL zu speichern JSONField.

Hier ist die allgemeine Lösung, nachdem Sie eine Weile gekämpft haben.

Der Schlüssel zu meiner Lösung besteht darin, den Quellcode von Python durchzugehen und zu erkennen, dass in der Codedokumentation ( hier beschrieben ) bereits erläutert wird, wie der vorhandene Code erweitert werden kann json.dumps, um andere Datentypen zu unterstützen.

Angenommen, Sie haben derzeit ein Modell, das einige Felder enthält, die nicht für JSON serialisierbar sind, und das Modell, das das JSON-Feld enthält, sieht ursprünglich folgendermaßen aus:

class SomeClass(Model):
    json_field = JSONField()

Definieren Sie einfach einen Benutzer JSONEncoderwie diesen:

class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
            return < whatever value you want >
        return json.JSONEncoder.default(self, obj)

    @staticmethod
    def json_dumper(obj):
        return json.dumps(obj, cls=CustomJsonEncoder)

Und dann benutze es einfach JSONFieldwie folgt:

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

Der Schlüssel ist die default(self, obj)obige Methode. ... is not JSON serializableFügen Sie für jede einzelne Beschwerde, die Sie von Python erhalten, einfach Code hinzu, um den Typ "unserializable to JSON" (z. B. Enumoder) zu verarbeitendatetime ) zu verarbeiten.

So unterstütze ich beispielsweise eine Klasse, die von Folgendes erbt Enum:

class TransactionType(Enum):
   CURRENT = 1
   STACKED = 2

   def default(self, obj):
       if isinstance(obj, TransactionType):
           return obj.value
       return json.JSONEncoder.default(self, obj)

Schließlich können Sie mit dem wie oben implementierten Code einfach alle Peewee-Modelle in ein JSON-seriazierbares Objekt wie das folgende konvertieren:

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

Obwohl der obige Code (etwas) spezifisch für Peewee war, denke ich:

  1. Es gilt allgemein für andere ORMs (Django usw.)
  2. Wenn Sie verstanden haben, wie es json.dumpsfunktioniert, funktioniert diese Lösung auch mit Python (ohne ORM) im Allgemeinen

Bei Fragen bitte in den Kommentaren posten. Vielen Dank!

Sivabudh
quelle
1

Diese Funktion verwendet die Rekursion, um jeden Teil des Wörterbuchs zu durchlaufen, und ruft dann die repr () -Methoden von Klassen auf, die keine integrierten Typen sind.

def sterilize(obj):
    object_type = type(obj)
    if isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif object_type in (list, tuple):
        return [sterilize(v) for v in obj]
    elif object_type in (str, int, bool):
        return obj
    else:
        return obj.__repr__()
Quinten Cabo
quelle
0

Ich habe meine eigene Lösung gefunden. Verwenden Sie diese Methode und übergeben Sie ein beliebiges Dokument ( Diktat , Liste , Objekt-ID usw.) zur Serialisierung.

def getSerializable(doc):
    # check if it's a list
    if isinstance(doc, list):
        for i, val in enumerate(doc):
            doc[i] = getSerializable(doc[i])
        return doc

    # check if it's a dict
    if isinstance(doc, dict):
        for key in doc.keys():
            doc[key] = getSerializable(doc[key])
        return doc

    # Process ObjectId
    if isinstance(doc, ObjectId):
        doc = str(doc)
        return doc

    # Use any other custom serializting stuff here...

    # For the rest of stuff
    return doc
Tauwelt
quelle
0

Ich entschied mich für die Verwendung von Dekoratoren, um das Problem der Serialisierung von datetime-Objekten zu lösen. Hier ist mein Code:

#myjson.py
#Author: jmooremcc 7/16/2017

import json
from datetime import datetime, date, time, timedelta
"""
This module uses decorators to serialize date objects using json
The filename is myjson.py
In another module you simply add the following import statement:
    from myjson import json

json.dumps and json.dump will then correctly serialize datetime and date 
objects
"""

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

    if isinstance(obj, (datetime, date)):
        serial = str(obj)
        return serial
    raise TypeError ("Type %s not serializable" % type(obj))


def FixDumps(fn):
    def hook(obj):
        return fn(obj, default=json_serial)

    return hook

def FixDump(fn):
    def hook(obj, fp):
        return fn(obj,fp, default=json_serial)

    return hook


json.dumps=FixDumps(json.dumps)
json.dump=FixDump(json.dump)


if __name__=="__main__":
    today=datetime.now()
    data={'atime':today, 'greet':'Hello'}
    str=json.dumps(data)
    print str

Durch den Import des obigen Moduls verwenden meine anderen Module json auf normale Weise (ohne Angabe des Standardschlüsselworts), um Daten zu serialisieren, die Datums- und Uhrzeitobjekte enthalten. Der datetime-Serializer-Code wird automatisch für json.dumps und json.dump aufgerufen.

John Moore
quelle
0

Ich mochte die Methode von Lost Koder am meisten. Beim Versuch, komplexere Objekte zu serialisieren, deren Mitglieder / Methoden nicht serialisierbar sind, sind Probleme aufgetreten. Hier ist meine Implementierung, die für mehr Objekte funktioniert:

class Serializer(object):
    @staticmethod
    def serialize(obj):
        def check(o):
            for k, v in o.__dict__.items():
                try:
                    _ = json.dumps(v)
                    o.__dict__[k] = v
                except TypeError:
                    o.__dict__[k] = str(v)
            return o
        return json.dumps(check(obj).__dict__, indent=2)
Will Charlton
quelle
0

Wenn Sie in der Lage sind, ein Paket zu installieren, würde ich empfehlen, Dill zu versuchen , was für mein Projekt gut funktioniert hat. Das Schöne an diesem Paket ist, dass es die gleiche Oberfläche hat wie pickle. Wenn Sie es also bereits picklein Ihrem Projekt verwendet haben, können Sie es einfach ersetzendill und prüfen, ob das Skript ausgeführt wird, ohne Code zu ändern. Es ist also eine sehr billige Lösung, es zu versuchen!

(Vollständige Geheimhaltung: Ich bin in keiner Weise mit dem Dill-Projekt verbunden und habe noch nie dazu beigetragen.)

Installieren Sie das Paket:

pip install dill

Bearbeiten Sie dann Ihren zu importierenden Code dillanstelle von pickle:

# import pickle
import dill as pickle

Führen Sie Ihr Skript aus und prüfen Sie, ob es funktioniert. (Wenn dies der Fall ist, möchten Sie möglicherweise Ihren Code bereinigen, damit Sie den Code nicht mehr beschattenpickle !)

Einige Details zu Datentypen, dilldie serialisiert werden können und nicht, finden Sie auf der Projektseite :

dill kann die folgenden Standardtypen beizen:

keine, Typ, Bool, Int, Long, Float, Komplex, Str, Unicode, Tupel, Liste, Diktat, Datei, Puffer, Integriert, sowohl alte als auch neue Stilklassen, Instanzen alter und neuer Stilklassen, Set, Frozenset, Array , Funktionen, Ausnahmen

dill kann auch mehr "exotische" Standardtypen einlegen:

Funktionen mit Ausbeuten, verschachtelten Funktionen, Lambdas, Zelle, Methode, ungebundene Methode, Modul, Code, Methodwrapper, Dictproxy, Methodendeskriptor, Getsetdescriptor, Memberdescriptor, Wrapperdescriptor, Xrange, Slice, Notimplemented, Ellipse, Quit

dill kann diese Standardtypen noch nicht einlegen:

Rahmen, Generator, Traceback

thedavidmo
quelle
0

Ich sehe hier keine Erwähnung von serieller Versionierung oder Backcompat, daher werde ich meine Lösung veröffentlichen, die ich seit einiger Zeit verwende. Ich kann wahrscheinlich noch viel mehr lernen, insbesondere Java und Javascript sind hier wahrscheinlich reifer als ich, aber hier geht es weiter

https://gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe

Fletch F Fletch
quelle
0

So fügen Sie eine weitere Option hinzu: Sie können das attrsPaket und die asdictMethode verwenden.

class ObjectEncoder(JSONEncoder):
    def default(self, o):
        return attr.asdict(o)

json.dumps(objects, cls=ObjectEncoder)

und zurück zu konvertieren

def from_json(o):
    if '_obj_name' in o:
        type_ = o['_obj_name']
        del o['_obj_name']
        return globals()[type_](**o)
    else:
        return o

data = JSONDecoder(object_hook=from_json).decode(data)

Klasse sieht so aus

@attr.s
class Foo(object):
    x = attr.ib()
    _obj_name = attr.ib(init=False, default='Foo')
Maschinenbauer
quelle
0

Zusätzlich zur Antwort des Onur möchten Sie möglicherweise den Datums- / Uhrzeittyp wie unten behandeln.
(Um zu behandeln: Das Objekt 'datetime.datetime' hat keine Attributausnahme ' dict '.)

def datetime_option(value):
    if isinstance(value, datetime.date):
        return value.timestamp()
    else:
        return value.__dict__

Verwendungszweck:

def toJSON(self):
    return json.dumps(self, default=datetime_option, sort_keys=True, indent=4)
Mark Choi
quelle
0

Zuerst müssen wir unser Objekt JSON-kompatibel machen, damit wir es mit dem Standard-JSON-Modul sichern können. Ich habe es so gemacht:

def serialize(o):
    if isinstance(o, dict):
        return {k:serialize(v) for k,v in o.items()}
    if isinstance(o, list):
        return [serialize(e) for e in o]
    if isinstance(o, bytes):
        return o.decode("utf-8")
    return o
Adi Degani
quelle
0

Aufbauend auf Quinten Cabo ‚s Antwort :

def sterilize(obj):
    if type(obj) in (str, float, int, bool, type(None)):
        return obj
    elif isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif hasattr(obj, '__iter__') and callable(obj.__iter__):
        return [sterilize(v) for v in obj]
    elif hasattr(obj, '__dict__'):
        return {k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']}
    else:
        return repr(obj)

Die Unterschiede sind

  1. Funktioniert für jedes iterable statt nur listund tuple((es funktioniert für NumPy-Arrays usw.)
  2. Funktioniert für dynamische Typen (solche, die a enthalten __dict__ ).
  3. Beinhaltet native Typen floatund wird Nonedaher nicht in Zeichenfolgen konvertiert.

Als Übung für den Leser bleibt es __slots__, Klassen zu behandeln , die sowohl iterierbar sind als auch Mitglieder haben, Klassen, die Wörterbücher sind und auch Mitglieder haben usw.

Mheyman
quelle