Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Forward exclude_defaults when jsonable_encoder recurses into dict values#15676

Open
vidigoat wants to merge 1 commit into
fastapi:masterfrom
vidigoat:fix-jsonable-encoder-exclude-defaults-dict
Open

Forward exclude_defaults when jsonable_encoder recurses into dict values#15676
vidigoat wants to merge 1 commit into
fastapi:masterfrom
vidigoat:fix-jsonable-encoder-exclude-defaults-dict

Conversation

@vidigoat

@vidigoat vidigoat commented Jun 1, 2026

Copy link
Copy Markdown

Summary

jsonable_encoder forwards exclude_defaults to its recursive calls for list/tuple/set items, but omits it when recursing into dict keys and values. So a Pydantic model nested inside a dict keeps its default-valued fields under exclude_defaults=True, while the same model inside a list correctly drops them:

from pydantic import BaseModel
from fastapi.encoders import jsonable_encoder

class M(BaseModel):
    foo: str
    bar: str = "bar"

m = M(foo="foo", bar="bar")
jsonable_encoder([m], exclude_defaults=True)        # [{'foo': 'foo'}]                      ✅
jsonable_encoder({"m": m}, exclude_defaults=True)   # {'m': {'foo': 'foo', 'bar': 'bar'}}    ❌ default kept

The sibling flags exclude_unset and exclude_none are already forwarded in that same dict branch, so this is a copy/paste inconsistency rather than intended behavior. (include/exclude are intentionally top-level-only and are correctly not forwarded.)

Fix

Forward exclude_defaults in the two recursive jsonable_encoder calls inside the dict branch (for encoded_key and encoded_value), matching how the list/tuple/set branch already does it.

Test plan

Added test_encode_model_with_default_in_container to tests/test_jsonable_encoder.py, asserting a model-in-dict behaves the same as model-in-list under exclude_defaults / exclude_unset.

# fails on master (dict keeps defaults), passes with this change:
python -m pytest tests/test_jsonable_encoder.py -q
# 27 passed
ruff check fastapi/encoders.py tests/test_jsonable_encoder.py   # All checks passed!
ruff format fastapi tests --check                                # clean

No behavior change for the already-correct list/tuple/set path; only the dict path is brought into line.

Disclosure: authored with the assistance of an AI tool; fully reviewed and verified by me.

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.
@codspeed-hq

codspeed-hq Bot commented Jun 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 20 untouched benchmarks


Comparing vidigoat:fix-jsonable-encoder-exclude-defaults-dict (df50247) with master (6926656)1

Open in CodSpeed

Footnotes

  1. No successful run was found on master (a5a68f1) during the generation of this report, so 6926656 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant