Browse Source

Preserve model field encoder options

pull/15654/head
jiyujie2006 5 days ago
parent
commit
a3ec86f189
  1. 27
      fastapi/encoders.py
  2. 46
      tests/test_jsonable_encoder.py

27
fastapi/encoders.py

@ -241,17 +241,42 @@ def jsonable_encoder(
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude) # type: ignore[assignment] # ty: ignore[invalid-assignment]
if isinstance(obj, BaseModel):
def custom_encoder_fallback(value: Any) -> Any:
if type(value) in custom_encoder:
encoded_value = custom_encoder[type(value)](value)
else:
for encoder_type, encoder_instance in custom_encoder.items():
if isinstance(value, encoder_type):
encoded_value = encoder_instance(value)
break
else:
raise TypeError(
f"Object of type {type(value).__name__} is not JSON serializable"
)
return jsonable_encoder(
encoded_value,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
obj_dict = obj.model_dump(
mode="python" if custom_encoder else "json",
mode="json",
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
exclude_defaults=exclude_defaults,
fallback=custom_encoder_fallback if custom_encoder else None,
)
return jsonable_encoder(
obj_dict,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
exclude_defaults=exclude_defaults,
custom_encoder=custom_encoder,

46
tests/test_jsonable_encoder.py

@ -12,7 +12,7 @@ import pytest
from fastapi._compat import Undefined
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import PydanticV1NotSupportedError
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_serializer
class Person:
@ -240,6 +240,50 @@ def test_custom_encoder_model_field():
) == {"value": "encoded"}
def test_custom_encoder_model_field_preserves_json_mode_serializers():
class CustomValue:
pass
class ModelWithJsonSerializer(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
created_at: datetime
value: CustomValue
@field_serializer("created_at", when_used="json")
def serialize_created_at(self, value):
return "serialized"
assert jsonable_encoder(
ModelWithJsonSerializer(
created_at=datetime(2024, 1, 1),
value=CustomValue(),
),
custom_encoder={CustomValue: lambda _: "encoded"},
) == {"created_at": "serialized", "value": "encoded"}
def test_custom_encoder_model_field_uses_caller_options():
class CustomValue:
pass
class EncodedValue(BaseModel):
required: int = Field(alias="Required")
optional: int = 2
class ModelWithCustomValue(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
value: CustomValue
assert jsonable_encoder(
ModelWithCustomValue(value=CustomValue()),
by_alias=False,
exclude_unset=True,
custom_encoder={CustomValue: lambda _: EncodedValue(Required=1)},
) == {"value": {"required": 1}}
def test_custom_enum_encoders():
def custom_enum_encoder(v: Enum):
return v.value.lower()

Loading…
Cancel
Save