是否可以在没有将编码器传递给json.dumps()的情况下在json中转储枚举?

时间:2016-04-18 16:30:51

标签: python json python-2.7 enums

我的问题可以通过以下示例进行总结:

from enum import Enum
import json

class FooBarType(Enum):
    standard = 0
    foo = 1
    bar = 2

dict = {'name': 'test', 'value': 'test', 'type': FooBarType.foo}

json.dumps(dict)

TypeError: <FooBarType.foo: 1> is not JSON serializable

我收到类型错误,因为枚举不是JSON可序列化的。

我主要是实施JsonEncoder并将其添加到json.dumps()来电,但我无法更改json.dumps()来电的行。

所以,我的问题是: 是否可以在不将编码器传递给json.dumps()的情况下在json中转储枚举,而是通过在FooBarType枚举中添加类方法?

我希望提取以下json:

{'name': 'test', 'value': 'test', 'type': 'foo'}

{'name': 'test', 'value': 'test', 'type': 1}

6 个答案:

答案 0 :(得分:11)

尝试:

from enum import Enum

# class StrEnum(str, Enum):
#     """Enum where members are also (and must be) strs"""

class Color(str, Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'


data = [
    {
        'name': 'car',
        'color': Color.RED,
    },
    {
        'name': 'dog',
        'color': Color.BLUE,
    },
]

import json
print(json.dumps(data))

结果:

[
    {
        "name": "car",
        "color": "red"
    },
    {
        "name": "dog",
        "color": "blue"
    }
]

答案 1 :(得分:3)

我不认为这有很好的方法,你会失去Enum的功能。

最简单的选择:不要枚举Enum:

class FooBarType:
    standard = 0
    foo = 1
    bar = 2

dict = {'type': FooBarType.foo}
json.dumps(dict)

你还可以做什么:

class EnumIntValue(int):
    def __new__(cls, name, value):
        c = int.__new__(cls, int(value))
        c.name = name
        return c
    def __repr__(self):
        return self.name
    def __str__(self):
        return self.name

class FooBarType:
    standard = EnumIntValue('standard',0)
    foo = EnumIntValue('foo',0)
    bar = EnumIntValue('bar',2)

dict = {'type': FooBarType.foo}
json.dumps(dict)

这实际上会给你

{"type": foo}

因此不是真正有效的json,但你可以随意使用它来满足你的需求!

答案 2 :(得分:3)

可悲的是,JSON中没有对Enum的直接支持。

最近的自动支持是使用IntEnumenum34也支持),然后json会将您的enum视为int s;当然,对它们进行解码会给你一个int后退,但是如果不指定你的编码器/解码器那就好了。

答案 3 :(得分:3)

只需在FooBarType枚举中添加方法就行不通。

正如我在评论中提到的,您可以使用我对问题Making object JSON serializable with regular encoder的部分答案来修补json模块,以便它返回{{1}的名称(或值) 1}}成员。我假设您正在使用Ethan Furman等人的enums34模块,该模块被反向移植到Python 2.7,因为该版本没有内置它 - 它成为standard library in Python 3.4的一部分

请注意,即使在应用补丁后发生发生Enum调用,也无法更改行。这是因为Python通常在json.dumps()中缓存import ed模块,即每次在单独的脚本中使用它们时都不会重新加载 - 因此对它们进行的任何更改都是“粘性的”并保持有效。

因此,对于您想要做的事情,首先要创建自己的模块来制作补丁。例如:sys.modules

make_enum_json_serializable.py

然后,在您自己的脚本中,您需要做的只是添加一行:

""" Module that monkey-patches the json module when it's imported so
JSONEncoder.default() automatically checks to see if the object being encoded
is an instance of an Enum type and, if so, returns its name.
"""
from enum import Enum
from json import JSONEncoder

_saved_default = JSONEncoder().default  # Save default method.

def _new_default(self, obj):
    if isinstance(obj, Enum):
        return obj.name  # Could also be obj.value
    else:
        return _saved_default

JSONEncoder.default = _new_default # Set new default method.

输出:

from enum import Enum
import json
import make_enum_json_serializable  # ADDED

class FooBarType(Enum):
    standard = 0
    foo = 1
    bar = 2

a_dict = {'name': 'spam', 'value': 42, 'type': FooBarType.foo}

print(json.dumps(a_dict))

答案 4 :(得分:1)

我最近遇到了一种情况,我必须序列化一个对象,该对象有几个 Enum 类型作为成员。

基本上,我刚刚添加了一个辅助函数,用于将枚举类型映射到它们的名称。

from enum import Enum, auto
from json import dumps

class Status(Enum):
    OK = auto()
    NOT_OK = auto()


class MyObject:
    def __init__(self, status):
        self.status = status


obja = MyObject(Status.OK)
objb = MyObject(Status.NOT_OK)

print(dumps(obja))
print(dumps(objb))

这当然会失败并返回错误 TypeError: Object of type MyObject is not JSON serializable,因为 status 实例的 MyObject 成员不可序列化。

from enum import Enum, auto
from json import dumps


def _prepare_for_serialization(obj):
    serialized_dict = dict()
    for k, v in obj.__dict__.items():
        serialized_dict[k] = v.name if isinstance(v, Enum) else v
    return serialized_dict


class Status(Enum):
    OK = auto()
    NOT_OK = auto()


class MyObject:
    def __init__(self, status):
        self.status = status


obja = MyObject(Status.OK)
objb = MyObject(Status.NOT_OK)

print(dumps(_prepare_for_serialization(obja)))
print(dumps(_prepare_for_serialization(objb)))

打印:

{"status": "OK"}
{"status": "NOT_OK"}

后来,我使用了相同的辅助函数来为序列化的 dict 挑选键。

答案 5 :(得分:0)

您可以使用元类代替枚举,也可以代替多重继承而没有这些副作用。

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

例如:

class FooBarType(metaclass=TypedEnum):
    standard = 0
    foo = 1
    bar = 2

这样每个实例都是一个整数,也是一个 FooBarType。

下面的元类。

class TypedEnum(type):
    """This metaclass creates an enumeration that preserves 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])
                ext_bases = (*bases, base)
                new_class = super().__new__(mcs, cls, ext_bases, classdict)
                setattr(new_class, "__member_names__", member_names)
            else:
                if not base == type(classdict[attr]):  # noqa
                    raise SyntaxError("Cannot mix types in TypedEnum")
            new_val = new_class.__new__(new_class, value)
            setattr(new_class, attr, new_val)

        for parent in bases:
            new_names = getattr(parent, "__member_names__", set())
            member_names |= new_names
            for attr in new_names:
                value = getattr(parent, attr)
                if not isinstance(value, base):
                    raise SyntaxError("Cannot mix inherited types in TypedEnum: %s from %s" % (attr, parent))
                # convert all inherited values to the new class
                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__))

    @property
    def __members__(cls):
        """Sufficient to make the @unique decorator work."""

        class FakeEnum:  # pylint: disable=too-few-public-methods
            """Object that looks a bit like an Enum instance."""

            def __init__(self, name, value):
                self.name = name
                self.value = value

        return {name: FakeEnum(name, getattr(cls, name)) for name in cls.__member_names__}

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