← 返回首页
Forward exclude_defaults when jsonable_encoder recurses into dict values by vidigoat · Pull Request #15676 · fastapi/fastapi · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

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 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 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

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.

Deadshot1831 commented Jul 7, 2026
edited
Loading

Copy link
Copy Markdown

I tested this locally against current master and this PR branch.

On master, exclude_defaults=True is applied correctly for models inside lists, but not for models inside dict values:

list: [{'foo': 'foo'}] dict: {'m': {'foo': 'foo', 'bar': 'bar', 'bla': 'bla'}}

On this PR branch, the dict case matches the list behavior:

list: [{'foo': 'foo'}] dict: {'m': {'foo': 'foo'}}

I also ran:

python -m pytest tests/test_jsonable_encoder.py -q 26 passed, 1 skipped

git diff --check master...review-pr-15676 passes, and git merge-tree --write-tree master review-pr-15676 exits successfully, so I don't see whitespace or merge-conflict issues. The change looks correctly scoped because exclude_defaults is a global serialization flag like exclude_unset and exclude_none, which are already forwarded in this dict branch.

Update: I also checked the red GitHub checks currently shown on the PR. check-labels is failing because the PR has no required category label yet. The failed Ubuntu coverage job log shows The runner has received a shutdown signal followed by The operation was canceled while setting up uv, before the test suite started, so I don't see evidence of a failing test assertion from this change.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

2 participants

Footer

© 2026 GitHub, Inc.