Browse Source

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.
pull/15676/head
Vidit Patankar 3 days ago
parent
commit
df50247add
  1. 2
      fastapi/encoders.py
  2. 13
      tests/test_jsonable_encoder.py

2
fastapi/encoders.py

@ -299,6 +299,7 @@ def jsonable_encoder(
key, key,
by_alias=by_alias, by_alias=by_alias,
exclude_unset=exclude_unset, exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none, exclude_none=exclude_none,
custom_encoder=custom_encoder, custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe, sqlalchemy_safe=sqlalchemy_safe,
@ -307,6 +308,7 @@ def jsonable_encoder(
value, value,
by_alias=by_alias, by_alias=by_alias,
exclude_unset=exclude_unset, exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none, exclude_none=exclude_none,
custom_encoder=custom_encoder, custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe, sqlalchemy_safe=sqlalchemy_safe,

13
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(): def test_custom_encoders():
class safe_datetime(datetime): class safe_datetime(datetime):
pass pass

Loading…
Cancel
Save