La guerre de la sérialisation a plus ou moins été gagnée par JSON. Le XML est relégué aux documents très riches et aux systèmes legacy. Le YML est cantonné a des niches (et en plus souvent des niches en Ruby, c’est dire !). Et les formats binaires, sont gardés pour les besoins de perf. Le reste, c’est du JSON, du JSON et du JSON.
Seulement le JSON ne permet pas de sauvegarder des dates, seulement des chaînes, des entiers, des booléens et null. Heureusement on peut créer son propre dialecte au dessus de JSON pour y remédier, mais il faut avoir un un parseur qui le gère.
En Python on peut créer sa propre classe d’encodeur et décodeur de JSON et donc techniquement ajouter n’importe quel type.
Voici une recette pour en créer un qui gère le type datetime
de manière transparente :
import re import json from datetime import datetime # On hérite simplement de l'encodeur de base pour faire son propre encodeur class JSONEncoder(json.JSONEncoder): # Cette méthode est appelée pour serialiser les objets en JSON def default(self, obj): # Si l'objet est de type datetime, on retourne une chaîne formatée # représentant l'instant de manière classique # ex: "2014-03-09 19:51:32.7689" if isinstance(obj, datetime): return obj.strftime('%Y-%m-%d %H:%M:%S.%f') return json.JSONEncoder.default(self, obj) # On fait l'opération exactement inverse pour le décodeur class JSONDecoder(json.JSONDecoder): # On écrase la méthode qui permet de décoder les paires clé / valeur # du format JSON afin que chaque valeur passe par notre moulinette def object_pairs_hook(self, obj): return dict((k, self.decode_on_match(v)) for k, v in obj) # notre moulinette def decode_on_match(self, obj): # une petite regex permet de savoir si la chaine est une date # sérialisée selon notre format précédent match = re.search(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}', unicode(obj)) # si oui, on parse et on retourne le datetime if match: return datetime.strptime(match.string, self.datetime_format) # sinon on retourne l'objet tel quel return obj # On se fait des raccourcis pour loader et dumper le json def json_dumps(data): return JSONEncoder().encode(data) def json_loads(string): return JSONDecoder().decode(string) |
Usage :
>>> res = json_dumps({'test': datetime(2000, 1, 1, 1, 1, 1), 'autre': [True, 1]}) >>> print(type(res), res) (<type 'str'>, '{"test": "2000-01-01 01:01:01.000000", "autre": [true, 1]}') >>> res = json_loads(res) >>> print(type(res), res) (<type 'dict'>, {u'test': u'2000-01-01 01:01:01.000000', u'autre': [True, 1]}) |
Minibelt contient une version un peu plus élaborée de ce code qui prend en compte les types date
, time
et timedelta
ainsi que pas mal d’options de configuration.
isinstance
? Erf…Pourquoi ne pas utiliser ça dans le sérialiseur plutôt ?
Rapide, pas cher, standard … et ça se fait bien manger par toutes les libs de tous les langages. Et puis après, il suffit d’avoir le désérialiseur idoine.
Comme Badu, je préfère la méthode .isoformat(), il vaut mieux garder les standards… ISO 8601 (UTC format)
+1, mieux vaut s’en tenir à isoformat, surtout qu’avec la méthode actuelle on perdrait les timezone des datetime conscient, ce qui est grave.
Tous des points parfaitement valides. Effectivement, ce seraient de bonnes améliorations.