Beschreibt ein Beispiel für die JSON-Codierung / -Decodierung komplexer Strukturen, die Instanzen (Objekte) benutzerdefinierter Klassen enthalten.
Die Sprache ist Python 3.8.1.
Für Python ist es einfacher, "pickle" zum Serialisieren komplexer Daten zu verwenden. Wenn Sie jedoch außerhalb von Python lesen oder schreiben möchten oder nach der Serialisierung eine gewisse Lesbarkeit wünschen, wählen Sie "JSON". Häufig.
Ich denke, es gibt andere Wege als die hier beschriebenen, aber ich mag diesen auf folgende Weise:
Da es zur Erklärung verwendet wird, wird es nicht so kompliziert sein, aber ich werde es mit Daten versuchen, die die folgenden Bedingungen erfüllen.
class Person:
def __init__(self, name):
self.name = name
class Robot:
def __init__(self, name, creator=None):
self.name = name
self.creator = creator
alan = Person('Alan')
beetle = Robot('Beetle', creator=alan)
chappy = Robot('Chappy', creator=beetle)
"Alan" ist ein Mensch und "Käfer" und "Chappy" sind Roboter. Im Folgenden möchte ich eine Liste von Roboterdaten erstellen und diese Liste codieren / decodieren.
robots = [beetle, chappy]
Das Serialisieren eines Objekts in eine JSON-Zeichenfolge wird als ** Codierung ** bezeichnet. Diese Liste enthält Objekte der Klassen "Person" und "Roboter", daher müssen Sie sie codieren können.
Beginnen wir mit einer einfachen Kodierung der Person-Klasse.
Um ein benutzerdefiniertes Objekt zu codieren, müssen Sie entscheiden, wie es codiert werden soll (die Spezifikation).
Hier werden ** Klassenname ** und ** Attributinhalt ** als Name-Wert-Paare ausgegeben. Im obigen Fall von "alan" wird angenommen, dass die JSON-Zeichenfolge wie folgt lautet.
{"class": "Person", "name": "Alan"}
Verwenden Sie einen benutzerdefinierten Encoder, indem Sie den Parameter cls
in der Standardfunktion [json.dumps] angeben (https://docs.python.org/ja/3/library/json.html#json.dumps). Ich kann.
Benutzerdefinierte Encoder werden erstellt, indem json.JSONEncoder geerbt und die Standardmethode überschrieben wird. ..
Da das Objekt im Argument der default
-Methode enthalten ist, ist es in Ordnung, wenn Sie es in einer Form zurückgeben, die von json.JSONEncoder
verarbeitet werden kann (hier dict
einschließlich nur str
).
import json
class PersonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Person):
return {'class': 'Person', 'name': obj.name}
else:
return super().default(obj)
print(json.dumps(alan, cls=PersonEncoder))
#Ergebnis:
{"class": "Person", "name": "Alan"}
Als nächstes erstellen wir einen Encoder der Klasse "Robot", aber das ist nicht kompliziert. Wie ich in der "Übersicht" geschrieben habe, ** ist die Codierungslogik durch die Klasse ** getrennt.
class RobotEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Robot):
return {'class': 'Robot', 'name': obj.name, 'creator': obj.creator}
else:
return super().default(obj)
Es ist fast dasselbe wie "PersonEncoder". Es läuft jedoch nicht so wie früher mit "PersonEncoder". Dies liegt daran, dass der "Ersteller" im Rückgabewert nicht in einer Form vorliegt, die von "json.JSONEncoder" verarbeitet werden kann. Ich wage es, die Logik auf diese Weise zu teilen und beim tatsächlichen Codieren die beiden Codierer zusammen zu verwenden.
Erstellen Sie eine neue Klasse mit Mehrfachvererbung, um die Encoder zusammenzuführen.
class XEncoder(PersonEncoder, RobotEncoder):
pass
print(json.dumps(robots, cls=XEncoder))
#Ergebnis:
[{"class": "Robot", "name": "Beetle", "creator": {"class": "Person", "name": "Alan"}}, {"class": "Robot", "name": "Chappy", "creator": {"class": "Robot", "name": "Beetle", "creator": {"class": "Person", "name": "Alan"}}}]
print(json.dumps(robots, cls=XEncoder, indent=4))
#Ergebnis:
[
{
"class": "Robot",
"name": "Beetle",
"creator": {
"class": "Person",
"name": "Alan"
}
},
{
"class": "Robot",
"name": "Chappy",
"creator": {
"class": "Robot",
"name": "Beetle",
"creator": {
"class": "Person",
"name": "Alan"
}
}
}
]
Dies liegt daran, dass Sie in der Funktion "json.dumps" nur eine Encoderklasse angeben können. Sie kann jedoch auch erweitert werden, wenn die Anzahl der Objekttypen zunimmt.
Ich werde kurz erklären, warum es funktioniert, indem ich den obigen "XEncoder" erstelle.
In Mehrfachvererbung von Python-Klassen wird auf die Attribute in der Reihenfolge der Vererbung verwiesen. Wenn Sie die "Standard" -Methode von "XEncoder" aufrufen, wechseln Sie zuerst zur "Standard" -Methode des geerbten "PersonEncoder".
Die Methode "PersonEncoder.default" gibt "dict" von selbst zurück, wenn "obj" ein "Person" -Objekt ist, andernfalls wird die Supermethode aufgerufen.
Die Supermethode in diesem Fall wäre RobotEncoder.default
** anstelle von ** json.JSONEncoder.default
.
Dies ist Pythons Mehrfachvererbungsbewegung.
Wenn RobotEncoder.default
eine Super-Methode aufruft, erbt sie nicht mehr, sodass die Verarbeitung an die ursprüngliche Superklasse json.JSONEncoder
delegiert wird.
Ich habe nicht untersucht, wie die "Standard" -Methode rekursiv aufgerufen wird, aber solange die "if" -Anweisung eine Klassenentscheidung trifft, scheint es, dass das gleiche Ergebnis erzielt werden kann, selbst wenn die Vererbungsreihenfolge umgekehrt wird.
Das Deserialisieren einer JSON-Zeichenfolge in ein Objekt im Gegensatz zur Codierung wird als ** Decodierung ** bezeichnet.
json.loads Durch Übergeben des Parameters object_hook
an die Methode wird eine benutzerdefinierte Verarbeitung auf das decodierte Objekt angewendet. Kann hinzugefügt werden.
Schauen wir uns zunächst ein Beispiel an, bei dem nur ein Objekt der Klasse "Person" codiert und dekodiert wird. Die als "object_hook" übergebene Funktion empfängt ein dekodiertes Objekt (z. B. "dict"). Schreiben Sie also, was zu tun ist, wenn der Wert von "class" "dict" ist, also "Person".
def person_hook(obj):
if type(obj) == dict and obj.get('class') == 'Person':
return Person(obj['name'])
else:
return obj
#In JSON-Zeichenfolge codieren
alan_encoded = json.dumps(alan, cls=PersonEncoder)
#Aus JSON-String dekodieren
alan_decoded = json.loads(alan_encoded, object_hook=person_hook)
print(alan_decoded.__class__.__name__, vars(alan_decoded))
#Ergebnis:
Person {'name': 'Alan'}
Erstellen Sie als Nächstes einen object_hook
für die Robot
-Klasse und erstellen Sie eine neue Funktion, die beide kombiniert.
def robot_hook(obj):
if type(obj) == dict and obj.get('class') == 'Robot':
return Robot(obj['name'], creator=obj['creator'])
else:
return obj
def x_hook(obj):
return person_hook(robot_hook(obj))
Die kombinierte Funktion "x_hook" kann auch wie folgt geschrieben werden: Es wird etwas länger dauern, aber es ist einfacher, die Anzahl der Haken zu erhöhen (die Reihenfolge des Anbringens von Haken unterscheidet sich vom obigen Beispiel, aber es gibt kein Problem).
def x_hook(obj):
hooks = [person_hook, robot_hook]
for hook in hooks:
obj = hook(obj)
return obj
Verwenden wir dies, um die Liste der oben erstellten Roboter zu codieren / decodieren.
#In JSON-Zeichenfolge codieren
robots_encoded = json.dumps(robots, cls=XEncoder)
#Aus JSON-String dekodieren
robots_decoded = json.loads(robots_encoded, object_hook=x_hook)
for robot in robots_decoded:
print(robot.__class__.__name__, vars(robot))
#Ergebnis:
Robot {'name': 'Beetle', 'creator': <__main__.Person object at 0x0000029A48D34CA0>}
Robot {'name': 'Chappy', 'creator': <__main__.Robot object at 0x0000029A48D38100>}
Wie bei der Codierung (wahrscheinlich, weil sie von innen rekursiv decodiert wird) hat das Ändern der Reihenfolge, in der die Hooks angewendet werden, das Ergebnis nicht geändert.
Tatsächlich kann die Codierungsseite angepasst werden, indem eine Funktion auf die gleiche Weise zugewiesen wird. Im Gegenteil, wenn Sie versuchen, die Decodierungsseite zu einer Unterklasse des Decoders zu machen, wird dies komplizierter.
Wenn Sie eine benutzerdefinierte Codierungslogik zusammenführen, ist es besser, die Methode zum Erstellen einer Unterklasse zu wählen, wenn Sie nur mit Mehrfachvererbung schreiben möchten, und die Methode zum Zuweisen einer Funktion, wenn Sie dem Stil auf der Decodierungsseite entsprechen möchten.
def person_default(obj):
if isinstance(obj, Person):
return {'class': 'Person', 'name': obj.name}
else:
return obj
def robot_default(obj):
if isinstance(obj, Robot):
return {'class': 'Robot', 'name': obj.name, 'creator': obj.creator}
else:
return obj
def x_default(obj):
defaults = [person_default, robot_default]
for default in defaults:
obj = default(obj)
return obj
print(json.dumps(robots, default=x_default))
#Ergebnis:
[{"class": "Robot", "name": "Beetle", "creator": {"class": "Person", "name": "Alan"}}, {"class": "Robot", "name": "Chappy", "creator": {"class": "Robot", "name": "Beetle", "creator": {"class": "Person", "name": "Alan"}}}]
Es gibt einige Probleme bei der Dekodierung. Im obigen Beispiel waren der erste dekodierte Roboter 'Beetle' und der'Chappy''Creator''Beetle 'ursprünglich dasselbe Objekt. Auch der "Schöpfer" dieser "Beelte", "Alan", war das gleiche Objekt.
Das obige Decodierungsverfahren reproduziert die Situation vor dem Codieren nicht vollständig, da es nicht bedeutet, dass das bereits erstellte Objekt erneut verwendet wird, weil es denselben Namen hat. Wenn Sie dies tun möchten, können Sie einen Mechanismus für die Klassen "Person" und "Roboter" erstellen, sodass Sie das entsprechende Objekt von "object_hook" erhalten können, indem Sie einfach den Namen angeben.