Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enum deserialization doesn't work with non-simple enum values #585

Open
morrison12 opened this issue Aug 19, 2024 · 4 comments
Open

Enum deserialization doesn't work with non-simple enum values #585

morrison12 opened this issue Aug 19, 2024 · 4 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@morrison12
Copy link

Enums with "complex" values, for example a tuple, can't be deserialized as shown from the example below.
In the example below, it appears it is because the values are deserialized as a list but only a tuple is permitted !?!
If the name and not the value(s) were serialized for Enums this could be avoided but would break the cases where the value is desired. Perhaps serialize the name for all but IntEnum and StrEnum ?

Example

"""
Example of serializing and deserializing an Enum with a "complex" value
"""

from dataclasses import dataclass
from enum import Enum, unique

from serde import serde
from serde.json import from_json, to_json


@unique
class B(Enum):
    X = ("one", "some info on one")
    Y = ("two", "some info on two")
    Z = ("three", "some info on three")


@serde
@dataclass
class C:
    m: B


original = C(B.Y)

as_json = to_json(original)
print(f"{as_json=}")
print(f"{B.Z.value=}")
nb = B(("three", "some info on three"))
print(f"{nb.value=}")
from_json = from_json(C, as_json)
print("de-serialized as:", as_json)

assert original == from_json

Output

as_json='{"m":["two","some info on two"]}'
B.Z.value=('three', 'some info on three')
nb.value=('three', 'some info on three')
Traceback (most recent call last):
  File "/Users/james/Projects/spt/examples/ts9.py", line 32, in <module>
    from_json = from_json(C, as_json)
                ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/james/.virtualenvs/spt/lib/python3.11/site-packages/serde/json.py", line 105, in from_json
    return from_dict(c, de.deserialize(s, **opts), reuse_instances=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/james/.virtualenvs/spt/lib/python3.11/site-packages/serde/de.py", line 533, in from_dict
    return from_obj(cls, o, named=True, reuse_instances=reuse_instances)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/james/.virtualenvs/spt/lib/python3.11/site-packages/serde/de.py", line 501, in from_obj
    raise SerdeError(e) from None
serde.compat.SerdeError: failed to coerce the field C.m value ['two', 'some info on two'] into B: ['two', 'some info on two'] is not a valid B

Environment Details

    pyserde==0.20.1
    Python 3.11.9
    Os: macOS-12.7.5-arm64-arm-64bit
    Machine: arm64
@yukinarit
Copy link
Owner

@morrison12 currently non-primitive enum value is not supported.

    def enum(self, arg: DeField[Any]) -> str:
        return f"{typename(arg.type)}({self.primitive(arg)})"

https://github.com/yukinarit/pyserde/blob/main/serde/de.py#L936-L937

I will take a look if I can easily support non-primitive enum value

@yukinarit yukinarit added the enhancement New feature or request label Aug 21, 2024
@yukinarit
Copy link
Owner

hmm currently pyserde relies on enum constructor for deserialization, but to support non-primitive enum value such as tuple requires to generate (de)serialize functions for enum. It is a quite bit of work..

@yukinarit yukinarit added the help wanted Extra attention is needed label Aug 22, 2024
@morrison12
Copy link
Author

hmm currently pyserde relies on enum constructor for deserialization, but to support non-primitive enum value such as tuple requires to generate (de)serialize functions for enum. It is a quite bit of work..

The one thought I had was to establish the convention (for pyserde) that only simple enum's serialize their value and the rest serialize their name and then on the deserialization side it would be only a matter of choosing Enum(x) or Enum[x] as the constructor depending on whether it was a "simple" enum or not. Simple would be presumably be int, float, complex, str, bool: but I don't know how simple, pardon the pun, it is to determine the value type on the deserialization side (esp. with things like Flag).

@morrison12
Copy link
Author

@morrison12 currently non-primitive enum value is not supported.

    def enum(self, arg: DeField[Any]) -> str:
        return f"{typename(arg.type)}({self.primitive(arg)})"

https://github.com/yukinarit/pyserde/blob/main/serde/de.py#L936-L937

I will take a look if I can easily support non-primitive enum value

The workaround isn't too horrible, something like:

class A(Enum):
...

@serde
@dataclass
class B:
....
   foo: A = serde_field(serializer=lambda x:x.name, deserializer=lambda x:A[x])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants