diff --git a/fastapi/encoders.py b/fastapi/encoders.py index c9f882d2b..59d788c89 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -250,12 +250,12 @@ def jsonable_encoder( exclude_none=exclude_none, exclude_defaults=exclude_defaults, ) - return jsonable_encoder( - obj_dict, - exclude_none=exclude_none, - exclude_defaults=exclude_defaults, - sqlalchemy_safe=sqlalchemy_safe, - ) + if not sqlalchemy_safe: + return obj_dict + return { + k: v for k, v in obj_dict.items() + if not (isinstance(k, str) and k.startswith("_sa")) + } if dataclasses.is_dataclass(obj): assert not isinstance(obj, type) obj_dict = dataclasses.asdict(obj) diff --git a/tests/test_encoder_performance.py b/tests/test_encoder_performance.py new file mode 100644 index 000000000..86f5bc00c --- /dev/null +++ b/tests/test_encoder_performance.py @@ -0,0 +1,52 @@ +from typing import List, Optional +from pydantic import BaseModel +from fastapi.encoders import jsonable_encoder + + +class SubModel(BaseModel): + name: str + value: int = 42 + + +class MainModel(BaseModel): + id: int + title: str + sub: SubModel + items: List[SubModel] + maybe: Optional[str] = None + _sa_instance_state: str = "should-be-removed" + + +def test_basemodel_serialization_correctness(): + sub = SubModel(name="test") + model = MainModel( + id=1, + title="hello", + sub=sub, + items=[sub, SubModel(name="another", value=10)], + ) + + # 1. Standard serialization + encoded = jsonable_encoder(model) + assert encoded == { + "id": 1, + "title": "hello", + "sub": {"name": "test", "value": 42}, + "items": [ + {"name": "test", "value": 42}, + {"name": "another", "value": 10}, + ], + "maybe": None, + } + + # 2. Exclude none + encoded_exclude_none = jsonable_encoder(model, exclude_none=True) + assert "maybe" not in encoded_exclude_none + + # 3. Include and Exclude parameter filtering + encoded_filtered = jsonable_encoder(model, include={"id", "title"}) + assert encoded_filtered == {"id": 1, "title": "hello"} + + encoded_excluded = jsonable_encoder(model, exclude={"sub", "items"}) + assert "sub" not in encoded_excluded + assert "items" not in encoded_excluded