Rendre la sérialisation plus robuste avec JSON et des types personnalisés

Un tutoriel détaille la construction, en Python, d’un processus de sérialisation JSON capable de gérer des types de données personnalisés, de tolérer des données malformées et de s’adapter à l’évolution des schémas. L’objectif est de faire circuler des jeux de données entre fichiers, processus, microservices et API sans que le pipeline s’interrompe à la première anomalie.

  • Le tutoriel montre comment fiabiliser la sérialisation JSON en Python.
  • Un encodeur personnalisé convertit datetime, set, Path et Enum.
  • Un décodeur tolérant restaure les types et journalise les données malformées.
  • Une fonction de chargement applique des valeurs par défaut face à l’évolution des schémas.

Selon l’article, seule une petite part du temps d’un ingénieur en machine learning est consacrée à l’écriture de l’architecture du modèle. Le reste revient à la plomberie des données (« data plumbing »), c’est-à-dire le nettoyage, le prétraitement et la transformation des données. Une partie de ce travail consiste à sérialiser les données, les convertir en texte pour les stocker ou les transmettre, puis à les désérialiser, c’est-à-dire les reconstruire en objets Python.

Le problème de départ tient aux limites du format JSON, qui ne connaît que quelques types de base. Un dictionnaire de configuration d’expérience contenant un Enum, un set, un Path ou un datetime provoque une erreur dès l’appel à json.dumps(), avec le message « TypeError: Object of type ModelType is not JSON serializable ».

Un encodeur personnalisé

La première étape consiste à définir une classe qui hérite de json.JSONEncoder et redéfinit sa méthode default(). Pour chaque type problématique, une condition de la forme isinstance() indique la conversion à effectuer. Les datetime sont ainsi transformés en dictionnaire contenant une valeur au format ISO, les set en listes, les Path en chaînes de caractères et les Enum en leur valeur.

Un détail joue un rôle central : l’encodeur ajoute une clé « __type__ » aux objets qui risquent de perdre leur nature d’origine, comme un set converti en liste. Une fois passé à json.dumps() via l’argument cls, le dictionnaire d’exemple est sérialisé sans erreur, et le datetime apparaît sous la forme « 2026-09-28T14:26:10.328310 ».

Un décodeur tolérant aux erreurs

Dans le sens inverse, la fonction robust_decoder est transmise à json.loads() par l’argument object_hook. Elle est appelée sur chaque dictionnaire imbriqué et reconstruit l’objet d’origine lorsque la clé « __type__ » est présente. Les conversions sont placées dans un bloc try, qui intercepte les erreurs ValueError et TypeError.

En cas de donnée malformée, la fonction consigne un avertissement via le module logging et renvoie la valeur brute, sans interrompre le traitement. L’article précise que ce comportement peut être remplacé par une valeur de repli ou un dictionnaire d’erreur spécifique. Après désérialisation, la date est bien reconnue comme un datetime et la liste comme un set.

Gérer l’évolution des schémas

Les schémas de métadonnées des modèles changent souvent : des hyperparamètres sont ajoutés, d’autres abandonnés, certaines métriques renommées. Pour y répondre, l’auteur propose une fonction de fabrique, load_experiment_config(), qui charge le JSON avec le décodeur puis applique des valeurs par défaut à l’aide de get().

L’exemple porte sur un hyperparamètre « learning_rate » absent de l’ancienne configuration, auquel est attribuée la valeur par défaut 0.001. Le même principe peut s’appliquer à tout nouveau paramètre. L’article mentionne également la possibilité de supprimer les champs obsolètes lors du chargement. Appliqué à la configuration d’origine, le chargement restitue les types attendus : un entier pour l’identifiant, un set pour les variables, un PosixPath pour le chemin, un datetime pour l’horodatage et un float pour le taux d’apprentissage.