From 613c4f591171d3b44f3e351916e7401093795eb8 Mon Sep 17 00:00:00 2001 From: Kevin Squire Date: Tue, 3 Dec 2024 18:19:56 -0800 Subject: [PATCH] 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'} ``` --- serde/se.py | 3 ++- tests/test_basics.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/serde/se.py b/serde/se.py index b74abe4e..a3cd7885 100644 --- a/serde/se.py +++ b/serde/se.py @@ -381,8 +381,9 @@ def serializable_to_obj(object: Any) -> Any: 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, diff --git a/tests/test_basics.py b/tests/test_basics.py index ea7c3188..94a1d628 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -662,7 +662,6 @@ class Foo: @pytest.mark.parametrize("se,de", format_msgpack) def test_inheritance(se, de): - @serde.serde class Base: a: int @@ -1113,3 +1112,23 @@ class Foo: 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