Enum 멤버를 JSON으로 직렬화


99

Enum결과 JSON을 다시 Python 객체로 역 직렬화 할 수 있도록 Python 멤버를 JSON으로 직렬화하려면 어떻게해야 합니까?

예를 들어 다음 코드는 다음과 같습니다.

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
고마워, 제로! 좋은 예입니다.
Ethan Furman

모듈 (예 : enumencoder.py)에 코드가있는 경우 파싱하는 클래스를 JSON에서 dict로 가져와야합니다. 예를 들어,이 경우 enumencoder.py 모듈에서 Status 클래스를 가져와야합니다 .
Francisco Manuel Garca Botella 2016 년

내 우려는 악성 호출 코드가 아니라 웹 서버에 대한 악의적 인 요청이었습니다. 앞서 언급했듯이 개인 데이터는 응답에 노출되거나 코드 흐름을 조작하는 데 사용될 수 있습니다. 답변을 업데이트 해 주셔서 감사합니다. 기본 코드 예제가 안전하다면 더 좋을 것입니다.
Jared Deckard

1
@JaredDeckard 내 사과, 당신이 옳았 고 나는 틀 렸습니다. 그에 따라 답변을 업데이트했습니다. 입력 해 주셔서 감사합니다! 이것은 교육적 (그리고 징계)이었습니다.
Zero Piraeus

이 옵션이 더 적절 if isinstance(obj, Enum):할까요?
user7440787

127

나는 이것이 오래되었다는 것을 알고 있지만 이것이 사람들을 도울 것이라고 생각합니다. 나는 방금이 정확한 문제를 겪었고 문자열 열거 형을 사용하고 있는지 발견하여 열거 형 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
감사. 나는 대부분 다중 상속에 반대하지만 꽤 깔끔하고 내가 가고있는 방식이다. 추가 인코더가 필요하지 않습니다. :)
Vinicius Dantas 19

@madjardi, 현재 겪고있는 문제에 대해 자세히 설명해 주시겠습니까? 열거 형의 속성 이름과 다른 문자열 값에 문제가 없었습니다. 귀하의 의견을 오해하고 있습니까?
Justin Carter

1
class LogLevel(str, Enum): DEBUG = 'Дебаг' INFO = 'Инфо'이 경우 enum with str제대로 작동하지 않습니다 (
madjardi

1
다른 기본 유형으로도이 트릭을 수행 할 수 있습니다. . 좋은 인코더에 대한 / O를 필요 w 덕분에,이 좋은 방법입니다!
NoCake

매력처럼 작동합니다, 감사합니다! 대신 답변으로 받아 들여야합니다.
Realfun

72

정답은 직렬화 된 버전으로 수행하려는 작업에 따라 다릅니다.

Python으로 다시 직렬화 해제하려면 Zero 's answer를 참조하십시오 .

직렬화 된 버전이 다른 언어로 이동하는 경우 IntEnum대신 해당 정수로 자동 직렬화되는를 사용할 수 있습니다 .

from enum import IntEnum
import json

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

json.dumps(Status.success)

그리고 이것은 다음을 반환합니다.

'0'

5
@AShelly : 질문에으로 태그가 지정되었으며이 답변은 3.4 이상 Python3.4입니다.
Ethan Furman 2014 년

2
완전한. 당신 열거가 문자열 인 경우, 다음을 사용 EnumMeta대신에IntEnum
bholagabbar

5
@bholagabbar : 아니, 당신은 사용하는 것이 Enum로모그래퍼 가능, str믹스 인 -class MyStrEnum(str, Enum): ...
에단 FURMAN

3
@bholagabbar, 흥미 롭습니다. 솔루션을 답변으로 게시해야합니다.
Ethan Furman

1
EnumMeta메타 클래스로만 의도 된 에서 직접 상속하는 것을 피할 것 입니다. 대신에, 음의 구현은 IntEnum 한 라이너 와 당신에 대해 동일한 달성 할 수 str와를 class StrEnum(str, Enum): ....
yungchin

18

Python 3.7에서는 json.dumps(enum_obj, default=str)


멋져 보이지만 namejson 문자열에 열거 형을 씁니다 . 더 나은 방법은 value열거 형 을 사용하는 것 입니다.
eNca 2010 년

Enum 값은 다음에서 사용할 수 있습니다.json.dumps(enum_obj, default=lambda x: x.value)
eNca

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데이터 모델 에 추가해야하는 이유는 무엇 입니까?
Yu Chen

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있습니까? 아니면 어딘가에 사용자 지정이 있습니까?
0x5453
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.