diff --git a/apischema/deserialization/__init__.py b/apischema/deserialization/__init__.py index 1fa063cb..1f394792 100644 --- a/apischema/deserialization/__init__.py +++ b/apischema/deserialization/__init__.py @@ -647,7 +647,11 @@ def factory(constraints: Optional[Constraints], _) -> DeserializationMethod: if fact.cls is not NoneType ) return OptionalMethod(value_method, self.coercer) - elif len(method_by_cls) == len(alt_factories): + elif len(method_by_cls) == len(alt_factories) and not any( + isinstance(x, CoercerMethod) for x in alt_methods + ): + # Coercion induces a different type in data than type to deserialize. + # Prefer UnionMethod in this case. return UnionByTypeMethod(method_by_cls) else: return UnionMethod(alt_methods) diff --git a/tests/integration/test_deserialize_with_coercion.py b/tests/integration/test_deserialize_with_coercion.py new file mode 100644 index 00000000..0eea512f --- /dev/null +++ b/tests/integration/test_deserialize_with_coercion.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass +from typing import Any, Mapping, Sequence, Union + +from apischema import deserialize + + +def _coerce_json(cls, data): + if not isinstance(data, cls) and isinstance(data, str): + return json.loads(data) + else: + return data + + +@dataclass +class MyClass: + my_property: Union[Sequence[str], Mapping[str, Any]] + + +def test_coerce_json(): + key = "test" + value = 2 + ret = deserialize( + MyClass, + { + "my_property": f'{{"{key}": {value}}}', + }, + coerce=_coerce_json, + ) + assert isinstance(ret, MyClass) + assert isinstance(ret.my_property, dict) and ret.my_property[key] == value diff --git a/tests/integration/test_object_fields_overriding.py b/tests/integration/test_object_fields_overriding.py index 4e5ae20f..f2cb940c 100644 --- a/tests/integration/test_object_fields_overriding.py +++ b/tests/integration/test_object_fields_overriding.py @@ -15,7 +15,8 @@ class Foo: @pytest.mark.skipif( - sys.version_info < (3, 8), reason="dataclasses.replace bug with InitVar" + (sys.version_info < (3, 8) or ((3, 9) < sys.version_info < (3, 9, 5))), + reason="dataclasses.replace bug with InitVar", ) def test_object_fields_overriding(): set_object_fields(Foo, [])