Repository: yukinarit/pyserde (python)

Issue:
pyserde unable to serialize a UUID inside of a dict[str, Any] in an object
Hi, the following serialization works in pyserde 0.14.1, but breaks in pyserde 0.14.2 (and later, up to 0.22.2)

```python
from uuid import UUID

@serde
@dataclass
class A:
    tags: dict[str, Any]

a = A(tags={'id': UUID('05b69d9a-3783-46ea-a811-35fa4b72ddac')})

to_dict(a)  # Works on 14.1, breaks on 14.2 (up to v0.22.2)
```

Here is the traceback:
```python
---------------------------------------------------------------------------
SerdeError                                Traceback (most recent call last)
Cell In[35], line 1
----> 1 to_dict(a)

File ~/Library/Caches/pypoetry/virtualenvs/test-env/lib/python3.10/site-packages/serde/se.py:461, in to_dict(o, c, reuse_instances, convert_sets)
    436 def to_dict(
    437     o: Any,
    438     c: Optional[Type[Any]] = None,
    439     reuse_instances: Optional[bool] = None,
    440     convert_sets: Optional[bool] = None,
    441 ) -> Dict[Any, Any]:
    442     """
    443     Serialize object into dictionary.
    444 
   (...)
    459     [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
    460     """
--> 461     return to_obj(  # type: ignore
    462         o, named=True, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets
    463     )

File ~/Library/Caches/pypoetry/virtualenvs/test-env/lib/python3.10/site-packages/serde/se.py:389, in to_obj(o, named, reuse_instances, convert_sets, c)
    386     return o
    388 except Exception as e:
--> 389     raise SerdeError(e) from None

SerdeError: maximum recursion depth exceeded while calling a Python object
```

Pull request:
Fix dict[str, Any] serialization
When a dict inside a class had values of type Any and the dict contained non-primitive types, pyserde would enter an infinite recusion.  See #617 for details.

The fix here causes non-primitive values to be serialized as strings when calling to_dict.  Arguably, this loses information, as a dict should be able to store the values without serializing them to strings first.  However, this same behavior exists when a dataclass variable is typed as Any and contains a non-primitive object (pyserde serializes it to a string):

```
import serde
import uuid
from typing import Any

@serde.serde
class A:
    a: Any

foo = A(uuid.UUID('05b69d9a-3783-46ea-a811-35fa4b72ddac'))
print(to_dict(a))  # {'a': '05b69d9a-3783-46ea-a811-35fa4b72ddac'}
```

Fixes #617

Patch:
diff --git a/serde/se.py b/serde/se.py
index b74abe4..a3cd788 100644
--- a/serde/se.py
+++ b/serde/se.py
@@ -381,8 +381,9 @@ def to_obj(
         elif is_bearable(o, dict):
             return {k: thisfunc(v) for k, v in o.items()}
         elif is_str_serializable_instance(o) or is_datetime_instance(o):
+            se_cls = o.__class__ if not c or c is Any else c
             return CACHE.serialize(
-                c or o.__class__,
+                se_cls,
                 o,
                 reuse_instances=reuse_instances,
                 convert_sets=convert_sets,


Test patch:
diff --git a/tests/test_basics.py b/tests/test_basics.py
index ea7c318..94a1d62 100644
--- a/tests/test_basics.py
+++ b/tests/test_basics.py
@@ -662,7 +662,6 @@ def test_skip_if_default(se, de):
 
 @pytest.mark.parametrize("se,de", format_msgpack)
 def test_inheritance(se, de):
-
     @serde.serde
     class Base:
         a: int
@@ -1113,3 +1112,23 @@ def test_init_var_with_field_attribute() -> None:
         a: int = serde.field(skip=True)
 
     assert serde.to_dict(Foo(10)) == {}
+
+
+def test_dict_str_any() -> None:
+    @serde.serde
+    class Foo:
+        a: dict[str, Any]
+
+    # Because the dict values are typed as Any, all values will be serialized as strings
+    date = datetime.datetime(2024, 12, 3, 16, 55, 20, 220662)
+    date_str = date.isoformat()
+
+    uuid_val = uuid.UUID("efb71d76-4337-4b27-a612-5b6f95b01d42")
+    uuid_str = str(uuid_val)
+
+    foo = Foo(a={"a": uuid_val, "b": date})
+    foo_se = {"a": {"a": uuid_str, "b": date_str}}
+    foo_de = Foo(a={"a": uuid_str, "b": date_str})
+
+    assert serde.to_dict(foo) == foo_se
+    assert serde.from_dict(Foo, foo_se) == foo_de