From df50247add73bc07719903512ce3c3452f3a5b29 Mon Sep 17 00:00:00 2001 From: Vidit Patankar Date: Mon, 1 Jun 2026 21:30:18 +0530 Subject: [PATCH] Forward exclude_defaults when jsonable_encoder recurses into dict values jsonable_encoder passes exclude_defaults to its recursive calls for list/tuple/set items, but omits it for dict keys and values. As a result a Pydantic model nested inside a dict keeps its default-valued fields when exclude_defaults=True, while the same model inside a list correctly drops them: jsonable_encoder([m], exclude_defaults=True) -> [{'foo': 'foo'}] # correct jsonable_encoder({'m': m}, exclude_defaults=True) -> {'m': {'foo': 'foo', ...}} # wrong, defaults kept The sibling flags exclude_unset and exclude_none are already forwarded in the same dict branch, so this is an inconsistency, not intended behavior. Forward exclude_defaults too. Adds a regression test. Authored with the assistance of an AI tool; reviewed and verified by me. --- fastapi/encoders.py | 2 ++ tests/test_jsonable_encoder.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index c9f882d2ba..e578768dac 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -299,6 +299,7 @@ def jsonable_encoder( key, by_alias=by_alias, exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, exclude_none=exclude_none, custom_encoder=custom_encoder, sqlalchemy_safe=sqlalchemy_safe, @@ -307,6 +308,7 @@ def jsonable_encoder( value, by_alias=by_alias, exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, exclude_none=exclude_none, custom_encoder=custom_encoder, sqlalchemy_safe=sqlalchemy_safe, diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index c23a9e5d79..ec04d3ca2e 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -202,6 +202,19 @@ def test_encode_model_with_default(): } +def test_encode_model_with_default_in_container(): + # exclude_defaults must be forwarded when recursing into dict values, just + # like it already is for list/tuple/set items. + model = ModelWithDefault(foo="foo", bar="bar") + assert jsonable_encoder([model], exclude_defaults=True) == [{"foo": "foo"}] + assert jsonable_encoder({"m": model}, exclude_defaults=True) == { + "m": {"foo": "foo"} + } + assert jsonable_encoder({"m": model}, exclude_unset=True) == { + "m": {"foo": "foo", "bar": "bar"} + } + + def test_custom_encoders(): class safe_datetime(datetime): pass