列挙型メンバーをJSONにシリアル化する


99

PythonEnumメンバーを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

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

回答:


54

任意のenum.EnumメンバーをJSONにエンコードしてから、(単に列挙型メンバーの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関数は、を使用してエンコードされたJSON EnumEncoder、またはそれと同じように動作するものに依存しています。

のメンバーへの制限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'}

1
ありがとう、ゼロ!良い例です。
イーサンファーマン2014年

モジュール(enumencoder.pyなど)にコードがある場合は、解析するクラスをJSONからdictにインポートする必要があります。たとえば、この場合、モジュールenumencoder.pyにクラスStatusをインポートする必要があります。
フランシスコマヌエルガルカボテッラ2016年

私の懸念は、悪意のある呼び出しコードではなく、Webサーバーへの悪意のある要求でした。あなたが述べたように、プライベートデータは応答で公開されるか、コードフローを操作するために使用される可能性があります。回答を更新していただきありがとうございます。ただし、メインのコード例が安全であればさらに良いでしょう。
Jared Deckard 2017年

1
@JaredDeckard申し訳ありませんが、あなたは正しかったし、私は間違っていました。それに応じて答えを更新しました。ご意見ありがとうございます!これは教育的(そして懲罰的)でした。
ゼロピレウス

このオプションはより適切if isinstance(obj, Enum):でしょうか?
user74407 8720

125

私はこれが古いことを知っていますが、これは人々を助けると思います。私はちょうどこの正確な問題を経験し、文字列列挙型を使用しているかどうかを発見しました。列挙型をサブクラスとして宣言する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を作成したくない場合に適したオプションです。


1
ありがとう。私は主に多重継承に反対していますが、それはかなりきちんとしていて、それが私が行っている方法です。追加のエンコーダは必要ありません:)
ViniciusDantas19年

@madjardi、あなたが抱えている問題について詳しく説明していただけますか?文字列の値が列挙型の属性の名前と異なるという問題は一度もありませんでした。私はあなたのコメントを誤解していますか?
ジャスティンカーター

1
class LogLevel(str, Enum): DEBUG = 'Дебаг' INFO = 'Инфо'この場合、enum with str適切な作業(なし
madjardi

1
たとえば、他の基本タイプでもこのトリックを実行できます(コメントでこれをフォーマットする方法はわかりませんが、要点は明らかです: "class Shapes(int、Enum):square = 1 circle = 2"は機能しますエンコーダの必要性O素晴らしいワット/おかげで、これは偉大なアプローチです。!
NoCake

72

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

Pythonに逆シリアル化する場合は、Zeroの回答を参照してください。

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

from enum import IntEnum
import json

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

json.dumps(Status.success)

そしてこれは戻ります:

'0'

5
@AShelly:質問はでタグ付けされてPython3.4おり、この回答は3.4以上に固有です。
イーサンファーマン2014

2
完璧です。列挙型が文字列の場合は、EnumMeta代わりにIntEnum
bholagabbar 2018

5
@bholagabbar:いいえ、Enumおそらくstrミックスインと一緒に使用します--– class MyStrEnum(str, Enum): ...
Ethan Furman

3
@bholagabbar、興味深い。あなたは答えとしてあなたの解決策を投稿するべきです。
イーサンファーマン2018

1
EnumMetaメタクラスとしてのみ意図されていた、から直接継承することは避けます。実装があること代わりに、ノートIntEnum ワンライナーであるとあなたが同じことを達成することができますstrclass StrEnum(str, Enum): ...
yungchin

17

Python 3.7では、 json.dumps(enum_obj, default=str)


見た目は良いですがname、列挙型をjson文字列に書き込みます。より良い方法はvalue、列挙型を使用することです。
eNca

列挙値は次のユーザーが使用できますjson.dumps(enum_obj, default=lambda x: x.value)
eNca 2010年

10

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)

これが誰かに役立つことを願っています。


なぜToJsonデータモデルに追加する必要があるのですか?
ゆうチェン

2

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 __name__ == '__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}}

-2

これは私のために働いた:

class Status(Enum):
    success = 0

    def __json__(self):
        return self.value

他に何も変更する必要はありませんでした。明らかに、これから値を取得するだけであり、後でシリアル化された値を列挙型に変換し直す場合は、他の作業を行う必要があります。


2
その魔法の方法を説明しているドキュメントには何も表示されません。他のJSONライブラリを使用していますJSONEncoderか、それともどこかにカスタムがありますか?
0x54 5320
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.