web-dev-qa-db-fra.com

Sérialisation d'un membre Enum vers JSON

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?

61
Bilal Syed Hussain

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'}
39
Zero Piraeus

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'
50
Ethan Furman

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é.

40
Justin Carter

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.

8
Pretzel

Dans Python 3.7, peut simplement utiliser json.dumps(enum_obj, default=str)

1
kai

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}}
0
rafalkasa