Пример
fastapi 0.141.1: Exclude default values in Pydantic models nested in dict keys and values when serializing with jsonable_encoder as fixed in FastAPI 0.140.9
Проверенный пример — pypi fastapi 0.141.1: Exclude default values in Pydantic models nested in dict keys and values when serializing with jsonable_encoder as…
sha256:b09b062b12f13c860852641882840dceca140b65e173f95222f347f4f95b25b6
Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла.
Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди.
MIT-0
Свидетельства выполнения
Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.
- Основа свидетельства
- Подписанный контракт пройден
- Квитанции проверки
- 2
- Ключи подписи, собравшие его
- 2
Заявленная среда
python linux x64 python python pip
Среды запусков проверки
| Окружение | Контракт | Этапы | Запуск |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-16 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
Кейс
HOW- Цель
- Exclude default values in Pydantic models nested in dict keys and values when serializing with jsonable_encoder as fixed in FastAPI 0.140.9
- Символы
-
- fastapi.encoders.jsonable_encoder
- fastapi.FastAPI
- fastapi.testclient.TestClient
- Окружение
- python
- Создан
- 2026-08-16T06:22:41Z
Контракт
- assert TestClient targets http://testserver, so the contract runs with network off and no DNS sockets
- assert jsonable_encoder([item], exclude_defaults=True) excludes default fields from items in top-level lists
- assert jsonable_encoder({'item': item}, exclude_defaults=True) propagates exclude_defaults into dict values instead of leaking default fields
- assert jsonable_encoder({'items': [item]}, exclude_defaults=True) propagates exclude_defaults through dicts containing lists
- assert jsonable_encoder({'data': {'items': [item]}}, exclude_defaults=True) propagates exclude_defaults through deeply nested dicts
- assert jsonable_encoder preserves default fields when exclude_defaults is False or omitted
- assert jsonable_encoder preserves non-default overridden fields when exclude_defaults is True
- assert exclude_defaults=True strips fields explicitly assigned their default value whereas exclude_unset=True retains them
- assert jsonable_encoder excludes defaults from nested Pydantic container models inside dictionaries
- assert jsonable_encoder({'data': null_model}, exclude_none=True) excludes None fields consistently
- assert FastAPI route responses returning dicts with exclude_defaults=True omit default fields over the wire
Файлы
- NOTES.md
- csx.json
- requirements.txt
- src/__init__.py
- src/app.py
- test/contract.py
Исходный код
# Notes: FastAPI jsonable_encoder exclude_defaults Dictionary Propagation Bugfix
## 1. Network Search Result
`search_known_solution` returned existing sample `sha256:8bcaa6f98cea6edf681d2b0f76a18856106de62aab70009b1d22a5a50abb1257` (*"Test a FastAPI app with TestClient and no network, and assert the validation error bodies it returns"*). That sample covers general endpoint validation error structures rather than patch release bugfixes.
## 2. Release and Notes Location
- **Release**: `fastapi 0.140.9` (Released July 28, 2026)
- **Source**: FastAPI GitHub Releases / Release Notes under `0.140.9` -> `### Fixes`:
> `* 🐛 Fix exclude_defaults not propagated to dict keys and values in jsonable_encoder. PR #16043 by @MBGrao.`
- **Pull Request**: PR [#16043](https://github.com/fastapi/fastapi/pull/16043) (*"Fix `exclude_defaults` not propagated to dict keys and values in `jsonable_encoder`"*)
## 3. What a Model Would Have Written Instead
A model would assume that passing `exclude_defaults=True` to `jsonable_encoder` (or returning dictionary structures containing Pydantic models from route handlers) recursively strips default fields uniformly across all container types (lists, tuples, sets, and mappings).
## 4. How the Wrong Version Fails
In FastAPI versions prior to `0.140.9`, the wrong version fails **silently with a green build**: `jsonable_encoder([model], exclude_defaults=True)` correctly omitted default fields, but `jsonable_encoder({"item": model}, exclude_defaults=True)` and `jsonable_encoder({"items": [model]}, exclude_defaults=True)` silently retained all default values because `fastapi/encoders.py` omitted `exclude_defaults` during dictionary key and value recursion, leaking default fields into serialization payloads and HTTP responses.
{"case":{"caseId":"case:sha256:a0061f9c2f248c9f7f4447ca5f2bd5c4aa3b888a61066f2d9caa529ae81035a8","contract":["assert TestClient targets http://testserver, so the contract runs with network off and no DNS sockets","assert jsonable_encoder([item], exclude_defaults=True) excludes default fields from items in top-level lists","assert jsonable_encoder({'item': item}, exclude_defaults=True) propagates exclude_defaults into dict values instead of leaking default fields","assert jsonable_encoder({'items': [item]}, exclude_defaults=True) propagates exclude_defaults through dicts containing lists","assert jsonable_encoder({'data': {'items': [item]}}, exclude_defaults=True) propagates exclude_defaults through deeply nested dicts","assert jsonable_encoder preserves default fields when exclude_defaults is False or omitted","assert jsonable_encoder preserves non-default overridden fields when exclude_defaults is True","assert exclude_defaults=True strips fields explicitly assigned their default value whereas exclude_unset=True retains them","assert jsonable_encoder excludes defaults from nested Pydantic container models inside dictionaries","assert jsonable_encoder({'data': null_model}, exclude_none=True) excludes None fields consistently","assert FastAPI route responses returning dicts with exclude_defaults=True omit default fields over the wire"],"goal":"Exclude default values in Pydantic models nested in dict keys and values when serializing with jsonable_encoder as fixed in FastAPI 0.140.9","kind":"HOW","packages":["pkg:pypi/fastapi@0.141.1","pkg:pypi/pydantic@2.13.4","pkg:pypi/starlette@1.6.0","pkg:pypi/httpx@0.28.1"],"schemaVersion":1,"symbols":["fastapi.encoders.jsonable_encoder","fastapi.FastAPI","fastapi.testclient.TestClient"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/fastapi@0.141.1","pkg:pypi/pydantic@2.13.4","pkg:pypi/starlette@1.6.0","pkg:pypi/httpx@0.28.1"],"schemaVersion":1,"symbols":["fastapi.encoders.jsonable_encoder","fastapi.FastAPI","fastapi.testclient.TestClient"],"verifierAdapter":"python@1"}
fastapi==0.141.1
starlette==1.6.0
annotated-doc==0.0.5
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.16.0
typing-inspection==0.4.4
annotated-types==0.8.0
anyio==4.14.2
idna==3.18
httpx==0.28.1
httpcore==1.0.9
h11==0.16.0
certifi==2026.7.22
"""Package root for FastAPI bugfix verification sample."""
"""FastAPI application demonstrating jsonable_encoder default-value exclusion behavior.
FastAPI 0.140.9 fixed a bug (PR #16043) where `jsonable_encoder` forwarded `exclude_defaults=True`
when recursing into lists, tuples, and sets, but silently dropped `exclude_defaults` when
recursing into dictionary keys and values.
In versions prior to 0.140.9:
- `jsonable_encoder([model], exclude_defaults=True)` stripped default fields as expected.
- `jsonable_encoder({"item": model}, exclude_defaults=True)` silently kept all default fields.
- `jsonable_encoder({"items": [model]}, exclude_defaults=True)` silently kept all default fields
because the outer dictionary recursion dropped `exclude_defaults` before reaching the inner list.
In FastAPI >= 0.140.9, dictionary recursion correctly propagates `exclude_defaults=exclude_defaults`,
producing consistent default exclusion across all container shapes.
"""
from typing import Any, Dict, List
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
class Item(BaseModel):
"""Pydantic model with default values, default_factory, and required fields."""
name: str = "default_name"
tag: str = "default_tag"
custom: str
generated: List[int] = Field(default_factory=lambda: [1, 2, 3])
class Container(BaseModel):
"""Container model nesting an Item."""
label: str = "default_label"
item: Item
app = FastAPI(title="FastAPI jsonable_encoder bugfix verification")
@app.get("/items/list")
def get_items_list() -> JSONResponse:
"""Returns a list of items encoded with exclude_defaults=True."""
item = Item(custom="custom_val")
encoded = jsonable_encoder([item], exclude_defaults=True)
return JSONResponse(content=encoded)
@app.get("/items/dict")
def get_items_dict() -> JSONResponse:
"""Returns a dictionary containing an item encoded with exclude_defaults=True."""
item = Item(custom="custom_val")
encoded = jsonable_encoder({"item": item}, exclude_defaults=True)
return JSONResponse(content=encoded)
@app.get("/items/nested")
def get_items_nested() -> JSONResponse:
"""Returns a nested dictionary with a list of items encoded with exclude_defaults=True."""
item = Item(custom="custom_val")
encoded = jsonable_encoder({"data": {"items": [item]}}, exclude_defaults=True)
return JSONResponse(content=encoded)
@app.get("/items/unset-vs-defaults")
def get_unset_vs_defaults() -> Dict[str, Any]:
"""Demonstrates difference between exclude_unset and exclude_defaults in dicts."""
# When a field is explicitly assigned its default value:
# - exclude_unset=True KEEPS the field (it was explicitly set)
# - exclude_defaults=True STRIPS the field (its value matches the default)
item_explicit_default = Item(name="default_name", custom="custom_val")
return {
"unset_excluded": jsonable_encoder(
{"item": item_explicit_default}, exclude_unset=True
),
"defaults_excluded": jsonable_encoder(
{"item": item_explicit_default}, exclude_defaults=True
),
}
import sys
from pathlib import Path
# Add project root to sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import fastapi
from fastapi.encoders import jsonable_encoder
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
from src.app import Container, Item, app
# --- 1. TestClient runs in-process with no network or DNS sockets --------
client = TestClient(app)
assert str(client.base_url) == "http://testserver"
# --- 2. jsonable_encoder: exclude_defaults propagation to dicts -----------
# In FastAPI < 0.140.9, jsonable_encoder passed exclude_defaults when recursing
# into lists/tuples/sets, but dropped it when recursing into dict keys/values.
# In FastAPI >= 0.140.9 (PR #16043), exclude_defaults propagates across all containers.
item = Item(custom="custom_val")
# Lists worked before and continue to work:
encoded_list = jsonable_encoder([item], exclude_defaults=True)
assert encoded_list == [{"custom": "custom_val"}]
# Dictionaries previously failed (leaking default_name, default_tag, generated=[1, 2, 3]):
encoded_dict = jsonable_encoder({"item": item}, exclude_defaults=True)
assert encoded_dict == {"item": {"custom": "custom_val"}}, (
f"Expected defaults excluded in dict value, got: {encoded_dict}"
)
# Lists nested inside dictionaries also previously failed because the outer dict dropped the flag:
encoded_dict_list = jsonable_encoder({"items": [item]}, exclude_defaults=True)
assert encoded_dict_list == {"items": [{"custom": "custom_val"}]}, (
f"Expected defaults excluded in dict-nested list, got: {encoded_dict_list}"
)
# Deeply nested dictionaries:
encoded_deep = jsonable_encoder({"data": {"items": [item]}}, exclude_defaults=True)
assert encoded_deep == {"data": {"items": [{"custom": "custom_val"}]}}, (
f"Expected defaults excluded in deeply nested dict, got: {encoded_deep}"
)
# --- 3. Default behavior preserves all default fields --------------------
encoded_default = jsonable_encoder({"item": item})
assert encoded_default == {
"item": {
"name": "default_name",
"tag": "default_tag",
"custom": "custom_val",
"generated": [1, 2, 3],
}
}
assert jsonable_encoder({"item": item}, exclude_defaults=False) == encoded_default
# --- 4. Overridden non-default fields are preserved -----------------------
item_override = Item(name="custom_name", custom="custom_val")
encoded_override = jsonable_encoder({"item": item_override}, exclude_defaults=True)
assert encoded_override == {"item": {"name": "custom_name", "custom": "custom_val"}}
# --- 5. Distinction between exclude_defaults and exclude_unset in dicts ---
# When a field is explicitly initialized to a value equal to its default:
# - exclude_unset=True keeps the field (it was explicitly set during instantiation)
# - exclude_defaults=True excludes the field (its value matches the model's default)
item_explicit_default = Item(name="default_name", custom="custom_val")
encoded_unset = jsonable_encoder(
{"item": item_explicit_default}, exclude_unset=True
)
assert encoded_unset == {
"item": {"name": "default_name", "custom": "custom_val"}
}
encoded_defaults = jsonable_encoder(
{"item": item_explicit_default}, exclude_defaults=True
)
assert encoded_defaults == {"item": {"custom": "custom_val"}}
# --- 6. Nested Pydantic container model serialization ---------------------
container = Container(item=item)
# Top-level model has label="default_label" (stripped) and nested item (defaults stripped)
encoded_container = jsonable_encoder(container, exclude_defaults=True)
assert encoded_container == {"item": {"custom": "custom_val"}}
# Container inside a dictionary
encoded_container_dict = jsonable_encoder(
{"container": container}, exclude_defaults=True
)
assert encoded_container_dict == {"container": {"item": {"custom": "custom_val"}}}
# --- 7. Sibling flag exclude_none operates consistently ------------------
class NullableModel(BaseModel):
required_val: str
optional_val: str | None = None
null_model = NullableModel(required_val="hello", optional_val=None)
encoded_none = jsonable_encoder({"data": null_model}, exclude_none=True)
assert encoded_none == {"data": {"required_val": "hello"}}
# --- 8. Endpoint responses via TestClient --------------------------------
res_list = client.get("/items/list")
assert res_list.status_code == 200
assert res_list.json() == [{"custom": "custom_val"}]
res_dict = client.get("/items/dict")
assert res_dict.status_code == 200
assert res_dict.json() == {"item": {"custom": "custom_val"}}
res_nested = client.get("/items/nested")
assert res_nested.status_code == 200
assert res_nested.json() == {"data": {"items": [{"custom": "custom_val"}]}}
res_comparison = client.get("/items/unset-vs-defaults")
assert res_comparison.status_code == 200
assert res_comparison.json() == {
"unset_excluded": {"item": {"name": "default_name", "custom": "custom_val"}},
"defaults_excluded": {"item": {"custom": "custom_val"}},
}
print(
f"contract ok: fastapi {fastapi.__version__} - "
"exclude_defaults correctly propagated to dict keys and values in jsonable_encoder"
)