yukinarit / pyserde

Yet another serialization library on top of dataclasses, inspired by serde-rs.
https://yukinarit.github.io/pyserde/guide/en
MIT License
730 stars 41 forks source link

Be able to serialize dicts with Enum keys to JSON #468

Open davetapley opened 9 months ago

davetapley commented 9 months ago

It would be nice if this worked:

from enum import Enum

from serde.json import to_json

class MyEnum(Enum):
    FOO = 'foo'
    BAR = 'bar'

my_dict: dict[MyEnum, str] = {
    MyEnum.FOO: 'foo',
    MyEnum.BAR: 'bar',
}

print(to_json(my_dict))

But currently (0.13.0) is:

Traceback (most recent call last):
  File "/workspaces/ng/serde_enum_dict.py", line 16, in <module>
    print(to_json(my_dict))
          ^^^^^^^^^^^^^^^^
  File "/opt/python/3.11.6/lib/python3.11/site-packages/serde/json.py", line 70, in to_json
    return se.serialize(to_dict(obj, c=cls, reuse_instances=False, convert_sets=True), **opts)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/python/3.11.6/lib/python3.11/site-packages/serde/json.py", line 45, in serialize
    return json_dumps(obj, **opts)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/python/3.11.6/lib/python3.11/site-packages/serde/json.py", line 19, in json_dumps
    return orjson.dumps(obj, **opts).decode()  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Dict key must be str
davetapley commented 9 months ago

I'd hoped to work around it with this ⬇️ but it doesn't work (same error) 😞


class Serializer:
    @dispatch
    def serialize(self, value: MyEnum) -> Any:
        return value.value

class Deserializer:
    @dispatch
    def deserialize(self, cls: Type[MyEnum], value: Any) -> MyEnum:
        return MyEnum(value)

serde.add_serializer(Serializer())
serde.add_deserializer(Deserializer())
davetapley commented 9 months ago

I wondered if wrapping in a class might help, but no luck:

class MyDict(dict[MyEnum, str]):
    ...

class Serializer:
    @dispatch
    def serialize(self, value: MyDict) -> dict[str, str]:
        return {k.value: v for k, v in value.items()}

class Deserializer:
    @dispatch
    def deserialize(self, cls: Type[MyDict], value: dict[str, str]) -> MyDict:

        return MyDict({MyEnum(k): v for k, v in value.items()})
davetapley commented 9 months ago

Ah, if I wrap it in a class (instead of inheriting) then it works out of the box (i.e. no serializer needed):

@dataclass
class MyClass:
    my_dict: dict[MyEnum, str]

my_class = MyClass({
    MyEnum.FOO: 'foo',
    MyEnum.BAR: 'bar',
})

json = to_json(my_class)
print(json)
my_class_ = from_json(MyClass, json)
print(my_class_)
{"my_dict":{"foo":"foo","bar":"bar"}}
MyClass(my_dict={<MyEnum.FOO: 'foo'>: 'foo', <MyEnum.BAR: 'bar'>: 'bar'})

That'll do as a workaround for now! ✅