web-dev-qa-db-ja.com

EnumメンバーをJSONにシリアル化する

Python EnumメンバーをJSONにシリアライズして、結果のJSONをPythonオブジェクトにデシリアライズできるようにするにはどうすればよいですか?

たとえば、次のコード:

from enum import Enum    
import json

class Status(Enum):
    success = 0

json.dumps(Status.success)

エラーになります:

TypeError: <Status.success: 0> is not JSON serializable

どうすればそれを回避できますか?

61

任意の_enum.Enum_メンバーをJSONにエンコードし、それを同じenumメンバーとしてデコードする場合(enumメンバーのvalue属性ではなく)、カスタムを記述することでそれを行うことができます- JSONEncoder クラス、および_object_hook_引数として json.load() または 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
_

_as_enum_関数は、EnumEncoder、またはそれと同じように動作する何かを使用してエンコードされたJSONに依存します。

_PUBLIC_ENUMS_のメンバーへの制限は、悪意を持って作成されたテキストを使用して、たとえばコードを呼び出してプライベート情報(たとえば、アプリケーションで使用される秘密キー)を無関係なデータベースフィールドに保存するのを避けるために必要ですその後、公開される可能性があります( http://chat.stackoverflow.com/transcript/message/35999686#35999686 を参照)。

使用例:

_>>> 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

正しい答えは、シリアル化されたバージョンをどうするかによって異なります。

Pythonへのシリアル化を解除する場合は、 ゼロの答え を参照してください。

シリアル化されたバージョンが別の言語に移行する場合、おそらく代わりにIntEnumを使用する必要があります。これは、対応する整数として自動的にシリアル化されます。

from enum import IntEnum
import json

class Status(IntEnum):
    success = 0
    failure = 1

json.dumps(Status.success)

そして、これは返します:

'0'
50
Ethan Furman

これは古いことは知っていますが、これは人々の役に立つと思います。私はちょうどこの正確な問題を調べて、文字列列挙を使用している場合、strのサブクラスとして列挙を宣言すると、ほとんどすべての状況でうまく機能することを発見しました。

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'))

出力されます:

LogLevel.DEBUG
"DEBUG"
DEBUG
LogLevel.DEBUG

ご覧のとおり、JSONをロードすると文字列DEBUGが出力されますが、LogLevelオブジェクトに簡単にキャストできます。カスタムJSONEncoderを作成したくない場合に適したオプションです。

40
Justin Carter

Zero Piraeusの回答が好きでしたが、Botoとして知られるAmazon Web Services(AWS)のAPIを操作するために、それをわずかに変更しました。

class EnumEncoder(json.JSONEncoder):
def default(self, obj):
    if isinstance(obj, Enum):
        return obj.name
    return json.JSONEncoder.default(self, obj)

次に、このメソッドをデータモデルに追加しました。

    def ToJson(self) -> str:
        return json.dumps(self.__dict__, cls=EnumEncoder, indent=1, sort_keys=True)

これが誰かの助けになることを願っています。

8
Pretzel

Python 3.7では、json.dumps(enum_obj, default=str)を使用できます

1
kai

jsonpickleを使用している場合、最も簡単な方法は次のようになります。

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)

Jsonのシリアル化の後、予想どおり{"status": 0} の代わりに

{"status": {"__objclass__": {"py/type": "__main__.Status"}, "_name_": "success", "_value_": 0}}
0
rafalkasa