Samouczek szczegółowo opisuje budowę, w Pythonie, procesu serializacji JSON zdolnego do obsługi niestandardowych typów danych, tolerowania źle sformatowanych danych i dostosowywania się do ewolucji schematów. Celem jest przesyłanie zbiorów danych między plikami, procesami, mikrousługami i API bez przerywania potoku przy pierwszej anomalii.
Według artykułu tylko niewielka część czasu inżyniera uczenia maszynowego jest poświęcona na pisanie architektury modelu. Reszta przypada na infrastrukturę danych („data plumbing »), czyli czyszczenie, wstępne przetwarzanie i transformację danych. Część tej pracy polega na serializacji danych, konwersji na tekst w celu przechowywania lub przesyłania, a następnie na ich deserializacji, czyli odtworzeniu w obiektach Pythona.
Problem wyjściowy wynika z ograniczeń formatu JSON, który obsługuje tylko kilka podstawowych typów. Słownik konfiguracyjny eksperymentu zawierający Enum, set, Path lub datetime powoduje błąd już przy wywołaniu json.dumps(), z komunikatem „TypeError: Object of type ModelType is not JSON serializable ».
Niestandardowy enkoder
Pierwszym krokiem jest zdefiniowanie klasy dziedziczącej po json.JSONEncoder i przedefiniowanie jej metody default(). Dla każdego problematycznego typu warunek w postaci isinstance() wskazuje konwersję do wykonania. Datetime są więc przekształcane w słownik zawierający wartość w formacie ISO, set w listy, Path w ciągi znaków, a Enum w ich wartość.
Jeden szczegół odgrywa kluczową rolę: enkoder dodaje klucz „__type__ » do obiektów, które mogą utracić swoją pierwotną naturę, jak set przekonwertowany na listę. Po przekazaniu do json.dumps() przez argument cls, przykładowy słownik jest serializowany bez błędu, a datetime pojawia się w postaci „2026-09-28T14:26:10.328310″.
Dekoder tolerujący błędy
W odwrotnym kierunku funkcja robust_decoder jest przekazywana do json.loads() przez argument object_hook. Jest wywoływana na każdym zagnieżdżonym słowniku i odtwarza oryginalny obiekt, gdy klucz „__type__ » jest obecny. Konwersje są umieszczane w bloku try, który przechwytuje błędy ValueError i TypeError.
W przypadku źle sformatowanych danych funkcja rejestruje ostrzeżenie za pomocą modułu logging i zwraca surową wartość, nie przerywając przetwarzania. Artykuł precyzuje, że to zachowanie można zastąpić wartością zastępczą lub specyficznym słownikiem błędu. Po deserializacji data jest poprawnie rozpoznawana jako datetime, a lista jako set.
Obsługa ewolucji schematów
Schematy metadanych modeli często się zmieniają: hiperparametry są dodawane, inne porzucane, niektóre metryki zmieniają nazwę. Aby temu sprostać, autor proponuje funkcję fabryczną load_experiment_config(), która ładuje JSON z użyciem dekodera, a następnie stosuje wartości domyślne za pomocą get().
Przykład dotyczy hiperparametru „learning_rate » nieobecnego w starej konfiguracji, któremu przypisano wartość domyślną 0.001. Ta sama zasada może być zastosowana do każdego nowego parametru. Artykuł wspomina również o możliwości usuwania przestarzałych pól podczas ładowania. Zastosowany do oryginalnej konfiguracji, proces ładowania przywraca oczekiwane typy: integer dla identyfikatora, set dla zmiennych, PosixPath dla ścieżki, datetime dla znacznika czasu i float dla współczynnika uczenia.
