Serialising an Enum member to JSON
Solution 1
If you want to encode an arbitrary enum.Enum
member to JSON and then decode
it as the same enum member (rather than simply the enum member's value
attribute), you can do so by writing a custom JSONEncoder
class, and a decoding function to pass as the object_hook
argument to json.load()
or 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
The as_enum
function relies on the JSON having been encoded using EnumEncoder
, or something which behaves identically to it.
The restriction to members of PUBLIC_ENUMS
is necessary to avoid a maliciously crafted text being used to, for example, trick calling code into saving private information (e.g. a secret key used by the application) to an unrelated database field, from where it could then be exposed (see https://chat.stackoverflow.com/transcript/message/35999686#35999686).
Example usage:
>>> 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'}
Solution 2
I know this is old but I feel this will help people. I just went through this exact problem and discovered if you're using string enums, declaring your enums as a subclass of str
works well for almost all 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'))
Will output:
LogLevel.DEBUG
"DEBUG"
DEBUG
LogLevel.DEBUG
As you can see, loading the JSON outputs the string DEBUG
but it is easily castable back into a LogLevel object. A good option if you don't want to create a custom JSONEncoder.
Solution 3
The correct answer depends on what you intend to do with the serialized version.
If you are going to unserialize back into Python, see Zero's answer.
If your serialized version is going to another language then you probably want to use an IntEnum
instead, which is automatically serialized as the corresponding integer:
from enum import IntEnum
import json
class Status(IntEnum):
success = 0
failure = 1
json.dumps(Status.success)
and this returns:
'0'
Solution 4
In Python >= 3.7, can just use
json.dumps(enum_obj, default=str)
If you want to use the enum value, you can do
json.dumps(enum_obj, default=lambda x: x.value)
or if you want to use the enum name,
json.dumps(enum_obj, default=lambda x: x.name)
Solution 5
You just need to inherit from str
or int
class:
from enum import Enum, unique
@unique
class StatusEnum(int, Enum):
pending: int = 11
approved: int = 15
declined: int = 266
That's it, it will be serialised using any JSON encoder.
Bilal Syed Hussain
Updated on December 04, 2021Comments
-
Bilal Syed Hussain over 2 years
How do I serialise a Python
Enum
member to JSON, so that I can deserialise the resulting JSON back into a Python object?For example, this code:
from enum import Enum import json class Status(Enum): success = 0 json.dumps(Status.success)
results in the error:
TypeError: <Status.success: 0> is not JSON serializable
How can I avoid that?
-
Bilal Syed Hussain almost 10 yearsI like to unserialize back into Python
-
Ethan Furman almost 10 yearsThanks, Zero! Nice example.
-
Sunilsingh over 9 yearsIn python3.2, This doesn't actually return what the answer says. I get:
>>> json.dumps(Status.success)
->'Status.success'
-
Ethan Furman over 9 years@AShelly: The question was tagged with
Python3.4
, and this answer is 3.4+ specific. -
Francisco Manuel Garca Botella almost 8 yearsIf you have your code in a module(enumencoder.py, for example), you must import the class that you parse from JSON to dict. For example, in this case, you must import the class Status in the module enumencoder.py.
-
Jared Deckard about 7 yearsMy concern was not about malicious calling code, but malicious requests to a web server. As you mentioned, the private data could be exposed in a response, or it could be used to manipulate code flow. Thank you for updating your answer. It would be even better if the main code example was secure though.
-
Zero Piraeus about 7 years@JaredDeckard my apologies, you were right, and I was wrong. I've updated the answer accordingly. Thanks for your input! This has been educational (and chastening).
-
gabbar0x about 6 yearsPerfect. If you Enum is a string, you would use
EnumMeta
instead ofIntEnum
-
Ethan Furman about 6 years@bholagabbar: No, you would use
Enum
, possibly with astr
mixin --class MyStrEnum(str, Enum): ...
-
gabbar0x about 6 yearsI used
EnumMeta
and the json serialises and deserialises perfectly. -
Ethan Furman about 6 years@bholagabbar, interesting. You should post your solution as an answer.
-
yungchin almost 5 yearsI would avoid inheriting directly from
EnumMeta
, which was intended as a metaclass only. Instead, note that the implementation ofIntEnum
is a one-liner and you can achieve the same forstr
withclass StrEnum(str, Enum): ...
. -
Vinicius Dantas over 4 yearsThanks. Even though I am mostly against multiple inheritances, that's pretty neat and that's the way I am going with. No extra encoder needed :)
-
Justin Carter over 4 years@madjardi, can you elaborate on the problem you are having? I have never had a problem with the value of the string being different than the name of the attribute in the enum. Am I misunderstanding your comment?
-
madjardi over 4 years
class LogLevel(str, Enum): DEBUG = 'Дебаг' INFO = 'Инфо'
in this caseenum with str
no work properly ( -
Asotos about 4 yearsWow! That and the
str
mixin cover most cases I'll ever need! -
user7440787 about 4 yearswould this option be more appropriate
if isinstance(obj, Enum):
? -
ed22 almost 4 yearsIs there a way to somehow 'annotate' the enum with the encoder class so that the encoder is used by default?
-
0x5453 almost 4 yearsI don't see anything in the docs describing that magic method. Are you using some other JSON library, or do you have a custom
JSONEncoder
somewhere? -
NoCake over 3 yearsYou can also do this trick with other base types, for example (I don't know how to format this in the comments, but the gist is clear: "class Shapes(int, Enum): square=1 circle=2" works great w/o need for an encoder. Thanks, this is a great approach!
-
eNca over 3 yearsLooks nice but it will write the
name
of enum into the json string. The better way will be to usevalue
of the enum. -
eNca over 3 yearsEnum value can be used by
json.dumps(enum_obj, default=lambda x: x.value)
-
Yu Chen over 3 yearsWhy do you need to add
ToJson
to your data model? -
Realfun over 3 yearsWorks like a charm, thanks! Should be accepted as answer instead.
-
Dequn over 3 yearsGreat solution, how do you find this.
-
koks der drache over 3 yearsThis str mixin can have unintended side effects: See this question.
-
FSCKur about 3 yearsPossibly this user is importing simplejson?
-
wr200m about 3 yearsHowever, this would not work if an Enum is used as a key. It would still complain of "TypeError: keys must be str, int, float, bool or None" - Most of the solutions above do not take care of the usage as key. Enum is supposed to be hashable.
-
Charles Naccio almost 3 yearsSuch an elegant solution! Thanks brother!
-
rjh over 2 yearsNote that this serializes to the enum value, in string form, e.g. "11" or "15".