Encoding Python Enum to JSON Encoding Python Enum to JSON json json

Encoding Python Enum to JSON


You can't use anything but strings as keys in dictionaries you want to convert to JSON. The encoder doesn't give you any other options; the default hook is only called for values of unknown type, never for keys.

Convert your keys to strings up front:

def convert_keys(obj, convert=str):    if isinstance(obj, list):        return [convert_keys(i, convert) for i in obj]    if not isinstance(obj, dict):        return obj    return {convert(k): convert_keys(v, convert) for k, v in obj.items()}json.dumps(convert_keys(test))

This recursively handles your dictionary keys. Note that I included a hook; you can then choose how to convert enumeration values to strings:

def enum_names(key):    if isinstance(key, TestEnum):        return key.name    return str(key)json.dumps(convert_keys(test, enum_names))

You can use the same function to reverse the process when loading from JSON:

def names_to_enum(key):    try:        return TestEnum[key]    except KeyError:        return keyconvert_keys(json.loads(json_data), names_to_enum)

Demo:

>>> def enum_names(key):...     if isinstance(key, TestEnum):...         return key.name...     return str(key)...>>> json_data = json.dumps(convert_keys(test, enum_names))>>> json_data'{"one": "This", "two": "should", "three": "work!"}'>>> def names_to_enum(key):...     try:...         return TestEnum[key]...     except KeyError:...         return key...>>> convert_keys(json.loads(json_data), names_to_enum){<TestEnum.one: 'first'>: 'This', <TestEnum.two: 'second'>: 'should', <TestEnum.three: 'third'>: 'work!'}


It is an old question. But no one gave this very simple answer.

You just need to subclass your Enum from str.

import jsonfrom enum import Enumclass TestEnum(str, Enum):    one = "first"    two = "second"    three = "third"test = {TestEnum.one : "This",        TestEnum.two : "should",        TestEnum.three : "work!"}print(json.dumps(test))

outputs:

{"first": "This", "second": "should", "third": "work!"}


I never use the builtin python enum anymore, I use a metaclass called "TypedEnum".

The reason is that the metaclass allows my string enums to act just like strings : they can be passed to functions that take strings, they can be serialized as strings (just like you want... right in a JSON encoding), but they are still a strong type (isA Enum) too.

https://gist.github.com/earonesty/81e6c29fa4c54e9b67d9979ddbd8489d

The number of weird bugs I've run into with regular enums is uncountable.

class TypedEnum(type):    """This metaclass creates an enumeration that preserve isinstance(element, type)."""    def __new__(mcs, cls, _bases, classdict):        """Discover the enum members by removing all intrinsics and specials."""        object_attrs = set(dir(type(cls, (object,), {})))        member_names = set(classdict.keys()) - object_attrs        member_names = member_names - set(name for name in member_names if name.startswith('_') and name.endswith('_'))        new_class = None        base = None        for attr in member_names:            value = classdict[attr]            if new_class is None:                # base class for all members is the type of the value                base = type(classdict[attr])                new_class = super().__new__(mcs, cls, (base, ), classdict)                setattr(new_class, "__member_names__", member_names)            else:                if not base == type(classdict[attr]):           # noqa                    raise SyntaxError("Cannot mix types in TypedEnum")            setattr(new_class, attr, new_class(value))        return new_class    def __call__(cls, arg):        for name in cls.__member_names__:            if arg == getattr(cls, name):                return type.__call__(cls, arg)        raise ValueError("Invalid value '%s' for %s" % (arg, cls.__name__))    def __iter__(cls):        """List all enum values."""        return (getattr(cls, name) for name in cls.__member_names__)    def __len__(cls):        """Get number of enum values."""        return len(cls.__member_names__)