Comment sérialiser un membre Python Enum
en JSON, afin de pouvoir désérialiser le JSON résultant en un objet Python?
Par exemple, ce code:
from enum import Enum
import json
class Status(Enum):
success = 0
json.dumps(Status.success)
entraîne l'erreur:
TypeError: <Status.success: 0> is not JSON serializable
Comment puis-je éviter cela?
Si vous souhaitez coder un membre enum.Enum
Arbitraire en JSON, puis le décoder comme le même membre enum (plutôt que simplement l'attribut value
du membre enum), vous pouvez le faire en écrivant un personnalisé - JSONEncoder
classe, et une fonction de décodage à passer comme argument object_hook
à json.load()
ou json.loads()
:
PUBLIC_ENUMS = {
'Status': Status,
# ...
}
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if type(obj) in PUBLIC_ENUMS.values():
return {"__enum__": str(obj)}
return json.JSONEncoder.default(self, obj)
def as_enum(d):
if "__enum__" in d:
name, member = d["__enum__"].split(".")
return getattr(PUBLIC_ENUMS[name], member)
else:
return d
La fonction as_enum
Repose sur le codage JSON à l'aide de EnumEncoder
, ou quelque chose qui se comporte de manière identique.
La restriction aux membres de PUBLIC_ENUMS
Est nécessaire pour éviter qu'un texte conçu de manière malveillante soit utilisé, par exemple, pour tromper l'appel de code en enregistrant des informations privées (par exemple, une clé secrète utilisée par l'application) dans un champ de base de données non lié, à partir de où il pourrait ensuite être exposé (voir http://chat.stackoverflow.com/transcript/message/35999686#35999686 ).
Exemple d'utilisation:
>>> data = {
... "action": "frobnicate",
... "status": Status.success
... }
>>> text = json.dumps(data, cls=EnumEncoder)
>>> text
'{"status": {"__enum__": "Status.success"}, "action": "frobnicate"}'
>>> json.loads(text, object_hook=as_enum)
{'status': <Status.success: 0>, 'action': 'frobnicate'}
La bonne réponse dépend de ce que vous avez l'intention de faire avec la version sérialisée.
Si vous allez annuler la sérialisation en Python, voir Zero's answer .
Si votre version sérialisée va dans une autre langue, vous voudrez probablement utiliser un IntEnum
à la place, qui est automatiquement sérialisé comme l'entier correspondant:
from enum import IntEnum
import json
class Status(IntEnum):
success = 0
failure = 1
json.dumps(Status.success)
et cela renvoie:
'0'
Je sais que c'est vieux mais je pense que cela aidera les gens. Je viens de passer par ce problème exact et j'ai découvert que si vous utilisez des énumérations de chaînes, déclarer vos énumérations comme une sous-classe de str
fonctionne bien dans presque toutes les situations:
import json
from enum import Enum
class LogLevel(str, Enum):
DEBUG = 'DEBUG'
INFO = 'INFO'
print(LogLevel.DEBUG)
print(json.dumps(LogLevel.DEBUG))
print(json.loads('"DEBUG"'))
print(LogLevel('DEBUG'))
Sortira:
LogLevel.DEBUG
"DEBUG"
DEBUG
LogLevel.DEBUG
Comme vous pouvez le voir, le chargement du JSON génère la chaîne DEBUG
mais elle peut facilement être convertie en objet LogLevel. Une bonne option si vous ne souhaitez pas créer un JSONEncoder personnalisé.
J'ai aimé la réponse de Zero Piraeus, mais je l'ai légèrement modifiée pour travailler avec l'API pour Amazon Web Services (AWS) connue sous le nom de Boto.
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.name
return json.JSONEncoder.default(self, obj)
J'ai ensuite ajouté cette méthode à mon modèle de données:
def ToJson(self) -> str:
return json.dumps(self.__dict__, cls=EnumEncoder, indent=1, sort_keys=True)
J'espère que ça aidera quelqu'un.
Dans Python 3.7, peut simplement utiliser json.dumps(enum_obj, default=str)
Si vous utilisez jsonpickle
, le moyen le plus simple devrait être le suivant.
from enum import Enum
import jsonpickle
@jsonpickle.handlers.register(Enum, base=True)
class EnumHandler(jsonpickle.handlers.BaseHandler):
def flatten(self, obj, data):
return obj.value # Convert to json friendly format
if __== '__main__':
class Status(Enum):
success = 0
error = 1
class SimpleClass:
pass
simple_class = SimpleClass()
simple_class.status = Status.success
json = jsonpickle.encode(simple_class, unpicklable=False)
print(json)
Après la sérialisation Json, vous aurez comme prévu {"status": 0}
au lieu de
{"status": {"__objclass__": {"py/type": "__main__.Status"}, "_name_": "success", "_value_": 0}}