From 5729e674d3d26bf9942d01782ce47e8d856b1b33 Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Wed, 17 Jun 2026 15:47:11 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20decimal=5Fencoder=20crash?= =?UTF-8?q?=20on=20Decimal("sNaN")=20(signaling=20NaN)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decimal('sNaN') is a valid Decimal value but float(Decimal('sNaN')) raises ValueError. The else branch in decimal_encoder always called float(), so any signaling NaN value reaching jsonable_encoder caused an unhandled crash. Fix: wrap the float() call in a try/except ValueError and fall back to float('nan') for signaling NaN, consistent with the existing behaviour for Decimal('NaN') (quiet NaN). Added test_decimal_encoder_signaling_nan to cover the regression. --- fastapi/encoders.py | 10 +++++++++- tests/test_jsonable_encoder.py | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index c9f882d2b..04859f85b 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -73,12 +73,20 @@ def decimal_encoder(dec_value: Decimal) -> int | float: >>> decimal_encoder(Decimal("NaN")) nan + + >>> decimal_encoder(Decimal("sNaN")) + nan """ exponent = dec_value.as_tuple().exponent if isinstance(exponent, int) and exponent >= 0: return int(dec_value) else: - return float(dec_value) + try: + return float(dec_value) + except ValueError: + # Signaling NaN (sNaN) cannot be converted to float directly. + # Treat it as a quiet NaN, consistent with Decimal("NaN") behaviour. + return float("nan") ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 8f8bd3fcb..e7efa6512 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -299,6 +299,14 @@ def test_decimal_encoder_infinity(): assert isinf(jsonable_encoder(data)["value"]) +def test_decimal_encoder_signaling_nan(): + # Decimal("sNaN") (signaling NaN) previously raised ValueError when + # decimal_encoder called float() on it. It should now return float("nan") + # consistently with Decimal("NaN") behaviour. + data = {"value": Decimal("sNaN")} + assert isnan(jsonable_encoder(data)["value"]) + + def test_encode_deque_encodes_child_models(): class Model(BaseModel): test: str