Skip to content

Commit

Permalink
Fix dict[str, Any] serialization
Browse files Browse the repository at this point in the history
    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'}
    ```
  • Loading branch information
kmsquire committed Dec 4, 2024
1 parent 2ae010d commit 613c4f5
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 2 deletions.
3 changes: 2 additions & 1 deletion serde/se.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 20 additions & 1 deletion tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,6 @@ class Foo:

@pytest.mark.parametrize("se,de", format_msgpack)
def test_inheritance(se, de):

@serde.serde
class Base:
a: int
Expand Down Expand Up @@ -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

0 comments on commit 613c4f5

Please sign in to comment.