Serialisierung mit JSON und benutzerdefinierten Typen robuster machen

Ein Tutorial beschreibt den Aufbau eines JSON-Serialisierungsprozesses in Python, der benutzerdefinierte Datentypen verarbeiten, fehlerhafte Daten tolerieren und sich an Schemaänderungen anpassen kann. Ziel ist es, Datensätze zwischen Dateien, Prozessen, Microservices und APIs zu übertragen, ohne dass die Pipeline bei der ersten Anomalie unterbrochen wird.

Laut dem Artikel wird nur ein kleiner Teil der Zeit eines Machine-Learning-Ingenieurs für das Schreiben der Modellarchitektur verwendet. Der Rest entfällt auf die plumbing der Daten („data plumbing »), also das Bereinigen, Vorverarbeiten und Transformieren der Daten. Ein Teil dieser Arbeit besteht darin, die Daten zu serialisieren, sie in Text umzuwandeln, um sie zu speichern oder zu übertragen, und anschließend zu deserialisieren, also sie als Python-Objekte wiederherzustellen.

Das Ausgangsproblem liegt in den Beschränkungen des JSON-Formats, das nur wenige Basistypen kennt. Ein Konfigurationswörterbuch für ein Experiment, das ein Enum, ein set, einen Path oder ein datetime enthält, verursacht bereits beim Aufruf von json.dumps() einen Fehler mit der Meldung „TypeError: Object of type ModelType is not JSON serializable ».

Ein benutzerdefinierter Encoder

Der erste Schritt besteht darin, eine Klasse zu definieren, die von json.JSONEncoder erbt und deren Methode default() überschreibt. Für jeden problematischen Typ gibt eine Bedingung in Form von isinstance() an, welche Umwandlung durchgeführt werden soll. So werden datetime in ein Wörterbuch mit einem ISO-formatierten Wert umgewandelt, set in Listen, Path in Zeichenketten und Enum in deren Wert.

Ein Detail spielt eine zentrale Rolle: Der Encoder fügt Objekten, die ihre ursprüngliche Natur verlieren könnten, wie eine in eine Liste umgewandelte set, einen Schlüssel „__type__ » hinzu. Wird das Beispielwörterbuch anschließend über das Argument cls an json.dumps() übergeben, erfolgt die Serialisierung fehlerfrei, und das datetime erscheint in der Form „2026-09-28T14:26:10.328310″.

Ein fehlertoleranter Decoder

Umgekehrt wird die Funktion robust_decoder über das Argument object_hook an json.loads() übergeben. Sie wird für jedes verschachtelte Wörterbuch aufgerufen und stellt das Originalobjekt wieder her, wenn der Schlüssel „__type__ » vorhanden ist. Die Umwandlungen sind in einem try-Block platziert, der ValueError- und TypeError-Ausnahmen abfängt.

Bei fehlerhaften Daten protokolliert die Funktion eine Warnung über das Modul logging und gibt den Rohwert zurück, ohne die Verarbeitung zu unterbrechen. Der Artikel weist darauf hin, dass dieses Verhalten durch einen Ersatzwert oder ein spezielles Fehlerwörterbuch ersetzt werden kann. Nach der Deserialisierung wird das Datum korrekt als datetime erkannt und die Liste als set.

Umgang mit Schemaänderungen

Die Metadatenschemata von Modellen ändern sich häufig: Hyperparameter werden hinzugefügt, andere fallen weg, manche Metriken werden umbenannt. Um dem zu begegnen, schlägt der Autor eine Fabrikfunktion load_experiment_config() vor, die das JSON mit dem Decoder lädt und anschließend mit get() Standardwerte anwendet.

Das Beispiel behandelt einen Hyperparameter „learning_rate », der in der alten Konfiguration fehlt und dem der Standardwert 0,001 zugewiesen wird. Dasselbe Prinzip kann auf jeden neuen Parameter angewandt werden. Der Artikel erwähnt auch die Möglichkeit, veraltete Felder beim Laden zu entfernen. Auf die ursprüngliche Konfiguration angewandt, liefert das Laden die erwarteten Typen zurück: eine Ganzzahl für die ID, ein set für die Variablen, ein PosixPath für den Pfad, ein datetime für den Zeitstempel und ein float für die Lernrate.