Wie mache ich eine Python-Klasse serialisierbar?
Eine einfache Klasse:
class FileItem:
def __init__(self, fname):
self.fname = fname
Was muss ich tun, um folgende Ausgabe zu erhalten:
json.dumps()
Ohne Fehler (FileItem instance at ... is not JSON serializable
)
Haben Sie eine Vorstellung von der erwarteten Leistung? Für z. wird das tun
>>> f = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'
In diesem Fall können Sie lediglich json.dumps(f.__dict__)
aufrufen.
Wenn Sie mehr benutzerdefinierte Ausgabe wünschen, müssen Sie JSONEncoder
subclass und Ihre eigene benutzerdefinierte Serialisierung implementieren.
Ein triviales 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 als cls
kwarg in die json.dumps()
-Methode:
json.dumps(cls=MyEncoder)
Wenn Sie auch dekodieren möchten, müssen Sie der JSONDecoder
- Klasse einen benutzerdefinierten object_hook
angeben. Für z.
>>> 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>
>>>
Hier ist eine einfache Lösung für eine einfache Funktion:
.toJSON()
MethodeImplementieren Sie anstelle einer serialisierbaren JSON-Klasse eine Serialisiermethode:
import json
class Object:
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
Also nennen Sie es einfach, um es zu serialisieren:
me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"
print(me.toJSON())
wird ausgeben:
{
"age": 35,
"dog": {
"name": "Apollo"
},
"name": "Onur"
}
Für komplexere Klassen können Sie das Werkzeug jsonpickle in Betracht ziehen:
jsonpickle ist eine Python-Bibliothek zur Serialisierung und Deserialisierung von komplexen Python-Objekten in und aus JSON.
Die Standard-Python-Bibliotheken zum Kodieren von Python in JSON, wie Json, Simplejson und Demjson der Stdlib, können nur Python-Primitiven verarbeiten, die ein direktes JSON-Äquivalent haben (z. B. Diktate, Listen, Strings, Ints usw.). jsonpickle baut auf diesen Bibliotheken auf und ermöglicht die Serialisierung komplexerer Datenstrukturen in JSON. jsonpickle ist in hohem Maße konfigurierbar und erweiterbar, sodass der Benutzer das JSON-Backend auswählen und zusätzliche Backends hinzufügen kann.
Bei den meisten Antworten wird der Aufruf in json.dumps () geändert, was nicht immer möglich oder wünschenswert ist (dies kann beispielsweise innerhalb einer Framework-Komponente geschehen).
Wenn Sie json.dumps (obj) aufrufen möchten, wie es ist, dann 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 immer Schlüssel explizit festlegen.
Eine weitere Option besteht darin, das 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, subclassing FileItem-Klasse von einer JsonSerializable
-Klasse:
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"}'
Ich mag Onurs Antwort , würde aber eine optionale toJSON()
-Methode für Objekte enthalten, um sich selbst zu serialisieren:
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
Neulich bin ich auf dieses Problem gestoßen und habe eine allgemeinere Version eines Encoders für Python-Objekte implementiert, der verschachtelte Objekte behandeln kann und vererbte Felder :
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"
}
Fügen Sie Ihrer Klasse einfach die to_json
-Methode hinzu:
def to_json(self):
return self.message # or how you want it to be serialized
Und fügen Sie diesen Code (from this answer ) an einer beliebigen Stelle oben ein:
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder().default
JSONEncoder.default = _default
Wenn dies importiert wird, wird das json-Modul monkey-patches JSONEncoder.default () sucht automatisch nach einer speziellen "to_json ()" -Methode und verschlüsselt das Objekt, wenn es gefunden wird.
Genau wie Onur gesagt hat, aber diesmal müssen Sie nicht jedes json.dumps()
in Ihrem Projekt aktualisieren.
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 den Standard json
verwenden, müssen Sie eine default
-Funktion definieren
import json
def default(o):
return o._asdict()
print(json.dumps(User('alice', '[email protected]'), default=default))
Diese Klasse kann den Trick, es konvertiert 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)
arbeit in python2.7
und python3
.
json
ist in Bezug auf die Objekte, die es drucken kann, begrenzt, und jsonpickle
(Sie benötigen möglicherweise einen pip install jsonpickle
) ist in der Hinsicht eingeschränkt, dass Text nicht eingerückt werden kann. Wenn Sie den Inhalt eines Objekts überprüfen möchten, dessen Klasse Sie nicht ändern können, kann ich immer noch keinen geraden Weg finden als:
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
Beachten Sie, dass die Objektmethoden immer noch nicht gedruckt werden können.
jaraco gab eine ziemlich nette Antwort. Ich musste ein paar Kleinigkeiten reparieren, aber das funktioniert:
# 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. Im Moment wird die __python__
-Eigenschaft nicht verwendet.
Mit der Methode von AlJohri überprüfe ich die Popularität von Ansätzen:
Serialisierung (Python -> JSON):
to_json
: 266.595 am 2018-06-27toJSON
: 96.307 am 2018-06-27__json__
: 8,504 am 27.06.2018for_json
: 6,937 am 2018-06-27Deserialisierung (JSON -> Python):
from_json
: 226,101 am 2018-06-27import 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)
jsonweb scheint die beste Lösung für mich zu sein. Siehe http://www.jsonweb.info/de/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"}'
Wenn Sie Python 3.5 verwenden, können Sie jsons
verwenden. Es konvertiert Ihr Objekt (und alle seine Attribute rekursiv) in ein Diktat.
import jsons
a_dict = jsons.dump(your_object)
Oder wenn Sie einen String haben wollten:
a_str = jsons.dumps(your_object)
Oder wenn Ihre Klasse jsons.JsonSerializable
implementiert hat:
a_dict = your_object.json
Wenn Sie nichts dagegen haben, 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_tricks
anstelle von 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 ist es im Grunde!
Dies wird im Allgemeinen gut funktionieren. Es gibt einige Ausnahmen, z. wenn in __new__
besondere Dinge passieren oder mehr Metaklassenzauber stattfindet.
Natürlich funktioniert auch das Laden (sonst, was ist der Punkt):
from json_tricks import loads
json_str = loads(json_str)
Dies setzt voraus, dass module_name.test_class.MyTestCls
importiert werden kann und nicht auf nicht kompatible Weise geändert wurde. Sie erhalten eine Instanz zurück, kein Wörterbuch oder etwas anderes, und es sollte eine identische Kopie zu der von Ihnen erstellten sein.
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
als Beispiel wird nur ein Teil der Attributparameter serialisiert.
Und als Gratis-Bonus erhalten Sie (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 wie Sie hatte.
Ich bin auf dieses Problem gestoßen, als ich versuchte, Peewees Modell in PostgreSQL JSONField
zu speichern.
Nachdem Sie sich eine Weile gekämpft haben, ist hier die allgemeine Lösung.
Der Schlüssel zu meiner Lösung besteht darin, den Quellcode von Python durchzugehen und zu erkennen, dass die Codedokumentation (hier hier ) bereits erläutert, wie der vorhandene json.dumps
zur Unterstützung anderer Datentypen erweitert wird.
Angenommen, Sie verfügen derzeit über ein Modell, das einige Felder enthält, die für JSON nicht serialisierbar sind, und das Modell, das das JSON-Feld enthält, sieht folgendermaßen aus:
class SomeClass(Model):
json_field = JSONField()
Definieren Sie einfach eine benutzerdefinierte JSONEncoder
wie folgt:
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 verwenden Sie es einfach in Ihrer JSONField
wie unten:
class SomeClass(Model):
json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)
Der Schlüssel ist die oben beschriebene default(self, obj)
-Methode. Für jede einzelne ... is not JSON serializable
-Beschwerde, die Sie von Python erhalten, fügen Sie einfach Code hinzu, der mit dem von unsialisierbaren JSON-Typ (wie Enum
oder datetime
) behandelt werden kann.
Zum Beispiel unterstütze ich eine Klasse, die von Enum
erbt:
class TransactionType(Enum):
CURRENT = 1
STACKED = 2
def default(self, obj):
if isinstance(obj, TransactionType):
return obj.value
return json.JSONEncoder.default(self, obj)
Wenn der Code wie oben implementiert ist, können Sie schließlich beliebige Peewee-Modelle in ein JSON-seriazierbares Objekt wie folgt konvertieren:
peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)
Der obige Code war zwar (etwas) spezifisch für Peewee, aber ich denke:
json.dumps
funktioniert, funktioniert diese Lösung generell auch mit Python (sans ORM)Fragen, bitte posten Sie in den Kommentaren. Vielen Dank!
Hier sind meine 3 Cent ...
Dies demonstriert die explizite Json-Serialisierung für ein baumartiges Python-Objekt.
Hinweis: Wenn Sie tatsächlich Code wie diesen wünschen, können Sie die twisted FilePath class 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)
Wenn Sie ein Paket installieren können, würde ich empfehlen, dill auszuprobieren, was für mein Projekt gut funktioniert hat. Das Schöne an diesem Paket ist, dass es dieselbe Schnittstelle wie pickle
hat. Wenn Sie pickle
bereits in Ihrem Projekt verwendet haben, können Sie einfach dill
einsetzen und sehen, ob das Skript ausgeführt wird, ohne Code zu ändern. Es ist also eine sehr günstige Lösung zum Ausprobieren!
(Vollständige Anti-Offenlegung: Ich bin in keiner Weise mit dem Dill-Projekt verbunden und habe nie dazu beigetragen.)
Installieren Sie das Paket:
pip install dill
Bearbeiten Sie dann Ihren Code, um dill
anstelle von pickle
zu importieren:
# 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 den Code bereinigen, damit Sie den Modulnamen pickle
nicht mehr spiegeln!)
Einige Details zu Datentypen, die dill
von der Projektseite serialisieren können und nicht können:
dill
kann die folgenden Standardtypen festlegen:none, type, bool, int, long, float, komplex, str, unicode, Tuple, list, dict, file, buffer, builtin, sowohl alte als auch neue Stilklassen, Instanzen alter und neuer Stilklassen, set, frozenset, array, Funktionen, Ausnahmen
dill
kann auch "exotische" Standardtypen verwenden:funktionen mit Ausbeuten, verschachtelten Funktionen, Lambdas, Zelle, Methode, Ungebundene Methode, Modul, Code, Methodwrapper, Dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, Wrapperdescriptor, Xrange, Slice, Notimplementiert, Ellipsis, Quit
dill
kann diese Standardtypen noch nicht festlegen:frame, Generator, Traceback
Ich habe meine eigene Lösung gefunden. Verwenden Sie diese Methode, um ein beliebiges Dokument ( dict , list , ObjectId etc) zu übergeben, um es zu serialisieren.
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
Dies ist eine kleine Bibliothek, die ein Objekt mit all seinen untergeordneten Objekten zu JSON serialisiert und es auch zurück analysiert:
Ich sehe hier keine Erwähnung von serieller Versionierung oder Backcompat, also werde ich meine Lösung posten, die ich für ein bisschen benutzt habe. Ich habe wahrscheinlich noch viel mehr zu lernen, insbesondere Java und Javascript ist wahrscheinlich ausgereifter als ich, aber hier ist es soweit
https://Gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe
Das hat gut für mich 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))
Die Methode von Lost Koder hat mir am besten gefallen. Beim Versuch, komplexere Objekte zu serialisieren, deren Mitglieder/Methoden nicht serialisierbar sind, bin ich auf Probleme gestoßen. Hier ist meine Implementierung, die mit mehr Objekten 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)
Ich habe mich entschieden, Dekoratoren zu verwenden, um das Serialisierungsproblem 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
Beim Importieren des obigen Moduls verwenden meine anderen Module json auf normale Weise (ohne das Standardschlüsselwort anzugeben), um Daten mit Datumsobjekten zu serialisieren. Der Datum-Uhrzeit-Serialisierercode wird automatisch für json.dumps und json.dump aufgerufen.