web-dev-qa-db-de.com

Wie man eine JSON-Klasse serialisierbar macht

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)

579
Sergey

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>
>>> 
437
Manoj Govindan

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

.toJSON() Methode

Implementieren 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"
}
490

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.

(LINK ZU JSONPICKLE AUF PYPI)

130
gecco

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.

54
andyhasit

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"}'
34
Paulo Freitas

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)
29
Jason S

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"
}
23
tobigue

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.

17
Fancy John
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))
9
tryer3000

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.

5
Lost Koder

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. 

5
ribamar

jaraco gab eine ziemlich nette Antwort. Ich musste ein paar Kleinigkeiten 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. Im Moment 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):

4
Martin Thoma
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)
3
rectangletangle

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"}'
2
matthewlent

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
2
R H

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.

1
Mark

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:

  1. Es ist allgemein auf andere ORMs (Django usw.) anwendbar
  2. Wenn Sie verstanden haben, wie json.dumps funktioniert, funktioniert diese Lösung generell auch mit Python (sans ORM)

Fragen, bitte posten Sie in den Kommentaren. Vielen Dank!

1
sivabudh

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)
1
Dan Brough

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

0
thedavidmo

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
0
Dewsworld

Dies ist eine kleine Bibliothek, die ein Objekt mit all seinen untergeordneten Objekten zu JSON serialisiert und es auch zurück analysiert:

https://github.com/Toubs/PyJSONSerialization/

0
Tobi

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

0
Fletch F Fletch

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

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)
0
Will Charlton

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.

0
John Moore