J'ai un Python set
qui contient des objets avec les méthodes __hash__
et __eq__
afin de s'assurer qu'aucun doublon ne soit inclus dans la collection.
J'ai besoin de json pour encoder ce résultat set
, mais le passage même d'un set
vide à la méthode json.dumps
lève un TypeError
.
File "/usr/lib/python2.7/json/encoder.py", line 201, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python2.7/json/encoder.py", line 264, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python2.7/json/encoder.py", line 178, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: set([]) is not JSON serializable
Je sais que je peux créer une extension de la classe json.JSONEncoder
avec une méthode personnalisée default
, mais je ne sais même pas par où commencer pour convertir set
. Devrais-je créer un dictionnaire à partir des valeurs set
dans la méthode par défaut, puis renvoyer le codage à ce sujet? Idéalement, j'aimerais que la méthode par défaut puisse gérer tous les types de données sur lesquels le codeur d'origine s'étouffe (j'utilise Mongo en tant que source de données, les dates semblent donc générer cette erreur également)
Tout indice dans la bonne direction serait apprécié.
EDIT:
Merci d'avoir répondu! J'aurais peut-être dû être plus précis.
J'ai utilisé (et voté) les réponses ici pour contourner les limitations du set
en cours de traduction, mais il y a des clés internes qui posent également un problème.
Les objets dans set
sont des objets complexes qui se traduisent par __dict__
, mais ils peuvent également contenir des valeurs pour leurs propriétés qui pourraient ne pas être éligibles pour les types de base du codeur json.
Il y a beaucoup de types différents entrant dans cette set
, et le hachage calcule en principe un identifiant unique pour l'entité, mais dans le véritable esprit de NoSQL, il est impossible de savoir exactement ce que contient l'objet enfant.
Un objet peut contenir une valeur de date pour starts
, tandis qu'un autre peut avoir un autre schéma n'incluant aucune clé contenant des objets "non primitifs".
C’est pourquoi la seule solution à laquelle je pouvais penser était d’étendre la méthode JSONEncoder
pour remplacer la méthode default
afin d’activer différents cas - mais je ne suis pas sûr de la marche à suivre et la documentation est ambiguë. . Dans les objets imbriqués, la valeur renvoyée par default
est-elle associée à une clé ou s'agit-il simplement d'un include/discard générique qui examine l'objet entier? Comment cette méthode gère-t-elle les valeurs imbriquées? J'ai parcouru les questions précédentes et je n'arrive pas à trouver la meilleure approche pour l'encodage spécifique à chaque cas (ce qui semble malheureusement être ce que je vais devoir faire ici).
La notation JSON n'a qu'une poignée de types de données natifs (objets, tableaux, chaînes, nombres, booléens et null), de sorte que tout ce qui est sérialisé en JSON doit être exprimé comme l'un de ces types.
Comme indiqué dans le documentation du module json , cette conversion peut être effectuée automatiquement par un JSONEncoder et JSONDecoder , mais vous abandonneriez une autre structure dont vous pourriez avoir besoin (si vous convertissez des ensembles en liste, vous perdez la possibilité de récupérer des listes régulières; si vous convertissez des ensembles en un dictionnaire utilisant dict.fromkeys(s)
alors vous perdez la possibilité de récupérer des dictionnaires).
Une solution plus sophistiquée consiste à créer un type personnalisé pouvant coexister avec d'autres types JSON natifs. Cela vous permet de stocker des structures imbriquées comprenant des listes, des ensembles, des dessins, des décimales, des objets datetime, etc.:
from json import dumps, loads, JSONEncoder, JSONDecoder
import pickle
class PythonObjectEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, (list, dict, str, unicode, int, float, bool, type(None))):
return JSONEncoder.default(self, obj)
return {'_python_object': pickle.dumps(obj)}
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(str(dct['_python_object']))
return dct
Voici un exemple de session montrant qu'il peut gérer des listes, des dict et des ensembles:
>>> data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]
>>> j = dumps(data, cls=PythonObjectEncoder)
>>> loads(j, object_hook=as_python_object)
[1, 2, 3, set(['knights', 'say', 'who', 'ni']), {u'key': u'value'}, Decimal('3.14')]
Alternativement, il peut être utile d'utiliser une technique de sérialisation plus générale, telle que YAML , Twisted Jelly , ou celle de Python pickle module . Ceux-ci prennent en charge une gamme beaucoup plus étendue de types de données.
Vous pouvez créer un encodeur personnalisé qui retourne un list
lorsqu'il rencontre un set
. Voici un exemple:
>>> import json
>>> class SetEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, set):
... return list(obj)
... return json.JSONEncoder.default(self, obj)
...
>>> json.dumps(set([1,2,3,4,5]), cls=SetEncoder)
'[1, 2, 3, 4, 5]'
Vous pouvez aussi détecter d’autres types. Si vous devez conserver le fait que la liste est en réalité un ensemble, vous pouvez utiliser un codage personnalisé. Quelque chose comme return {'type':'set', 'list':list(obj)}
pourrait fonctionner.
Pour illustrer les types imbriqués, envisagez de sérialiser ceci:
>>> class Something(object):
... pass
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
Cela soulève l'erreur suivante:
TypeError: <__main__.Something object at 0x1691c50> is not JSON serializable
Cela indique que l'encodeur prendra le résultat list
renvoyé et appellera le sérialiseur de manière récursive sur ses enfants. Pour ajouter un sérialiseur personnalisé à plusieurs types, procédez comme suit:
>>> class SetEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, set):
... return list(obj)
... if isinstance(obj, Something):
... return 'CustomSomethingRepresentation'
... return json.JSONEncoder.default(self, obj)
...
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
'[1, 2, 3, 4, 5, "CustomSomethingRepresentation"]'
Je me suis adapté solution de Raymond Hettinger à python 3.
Voici ce qui a changé:
unicode
disparudefault
des parents avec super()
base64
pour sérialiser le type bytes
en str
(car il semble que bytes
dans python 3 ne puisse pas être converti en JSON)from decimal import Decimal
from base64 import b64encode, b64decode
from json import dumps, loads, JSONEncoder
import pickle
class PythonObjectEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, (list, dict, str, int, float, bool, type(None))):
return super().default(obj)
return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
return dct
data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]
j = dumps(data, cls=PythonObjectEncoder)
print(loads(j, object_hook=as_python_object))
# prints: [1, 2, 3, {'knights', 'who', 'say', 'ni'}, {'key': 'value'}, Decimal('3.14')]
Seuls les dictionnaires, les listes et les types d'objet primitif (int, string, bool) sont disponibles en JSON.
Si vous avez seulement besoin d'encoder des ensembles, et non de simples objets Python, et que vous souhaitez le garder facilement lisible par l'homme, une version simplifiée de la réponse de Raymond Hettinger peut être utilisée:
import json
import collections
class JSONSetEncoder(json.JSONEncoder):
"""Use with json.dumps to allow Python sets to be encoded to JSON
Example
-------
import json
data = dict(aset=set([1,2,3]))
encoded = json.dumps(data, cls=JSONSetEncoder)
decoded = json.loads(encoded, object_hook=json_as_python_set)
assert data == decoded # Should assert successfully
Any object that is matched by isinstance(obj, collections.Set) will
be encoded, but the decoded value will always be a normal Python set.
"""
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
else:
return json.JSONEncoder.default(self, obj)
def json_as_python_set(dct):
"""Decode json {'_set_object': [1,2,3]} to set([1,2,3])
Example
-------
decoded = json.loads(encoded, object_hook=json_as_python_set)
Also see :class:`JSONSetEncoder`
"""
if '_set_object' in dct:
return set(dct['_set_object'])
return dct