diff --git a/changelog/fixed/tumbling-toucan.md b/changelog/fixed/tumbling-toucan.md new file mode 100644 index 000000000..c72f87bf3 --- /dev/null +++ b/changelog/fixed/tumbling-toucan.md @@ -0,0 +1 @@ +Typed JSON payload conversion no longer truncates a float to an `int` when the type hint is a union of `int` and `float`, such as `int | float`. diff --git a/temporalio/converter/_payload_converter.py b/temporalio/converter/_payload_converter.py index 8b428c229..f7579a703 100644 --- a/temporalio/converter/_payload_converter.py +++ b/temporalio/converter/_payload_converter.py @@ -900,6 +900,10 @@ def value_to_type( # Union if is_union: + # The int conversion accepts a float and truncates it, so give a float + # to a float member before trying an earlier int member + if isinstance(value, float) and float in type_args: + return value_to_type(float, value, custom_converters) # Try each one. Note, Optional is just a union w/ none. for arg in type_args: try: diff --git a/tests/test_converter.py b/tests/test_converter.py index 930c92d09..4862e077a 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -675,6 +675,11 @@ def fail(hint: Any, value: Any) -> None: fail(int | None, "1") ok(MyDataClass | NestedDataClass, MyDataClass("foo", 5, SerializableEnum.FOO)) ok(MyDataClass | NestedDataClass, NestedDataClass("foo")) + # An earlier int member must not truncate a float + ok(int | float, 5) + ok(int | float, 5.5) + ok(int | float | None, 5.5) + ok(list[int | float], [5, 5.5]) # NewType ok(NewIntType, 5)