Cannot control json serialization with custom response_class #8947
-
With dumps being a custom function, managing datetime values specifically.
Will work as expected but
Will fail to use the custom dump function. The cause is in https://github.com/tiangolo/fastapi/blob/master/fastapi/routing.py#L190 : serialize_response (which calls jsonable_encoder) is called before the response_class instantiation (https://github.com/tiangolo/fastapi/blob/master/fastapi/routing.py#L201) so datetime values are converted to str prematurely. |
Beta Was this translation helpful? Give feedback.
Replies: 9 comments · 2 replies
-
datetime encoder is coming from pydantic : https://github.com/samuelcolvin/pydantic/blob/master/pydantic/json.py#L20 i guess it's still possible to alter this one if we need custom encoder for datetime. |
Beta Was this translation helpful? Give feedback.
-
@TTRh : the explicit return of an CustomJSONResponse instance works fine without having to tweak pydantic. My point is that I wouldn't have expected a different behavior between the two syntaxes. |
Beta Was this translation helpful? Give feedback.
-
Have the same issue. Did you solve it? |
Beta Was this translation helpful? Give feedback.
-
Only a PR would really solve it, provided it's considered as an issue, hence my "question" here.
|
Beta Was this translation helpful? Give feedback.
-
@kshilov also you can create a workaround with class CustomJSONResponse(JSONResponse):
media_type = "application/json"
def render(self, content: typing.Any) -> bytes:
return dumps(content, default=str).encode() This will work however, I agree with @tangi75 it is a serious bug and should be handled by a PR. |
Beta Was this translation helpful? Give feedback.
-
I have the same issue where I want Looks like on the Any chance of that making to a PR or it that too much of a change that it would break things? Thanks |
Beta Was this translation helpful? Give feedback.
-
+1 I'm currently using FastAPI for an API migration and want to use a custom json encoder for parity reasons. A small example of what I'm seeing (this timestamp is from a pandas dataframe):
Current FastAPI code(shortened version of the current code in raw_response = await run_endpoint_function(...)
response_data = await serialize_response(response_content=raw_response, ...)
response = actual_response_class(response_data, **response_args)
It seems like the recommendation in this thread is to explicitly return a custom Would it be possible to either:
|
Beta Was this translation helpful? Give feedback.
-
@nguyent +1 on this, I am using mashumaro. |
Beta Was this translation helpful? Give feedback.
-
I experienced this from a slightly different direction. Writing up my experience in case anyone has ideas for me or in case this will help anyone else: I return responses including various custom types which aren't json-serializable and which aren't serializable by pydantic by default. Usually this looks something like this: class SomeModel(BaseModel):
regular_field: str
nothing_special_here: int
arbitrary_data: Dict[str, Any] I didn't want to add custom serialization at the pydantic level in each model because it would be repetitive. Before upgrading to pydantic 2, I added custom serialization at the FastAPI level using a custom response class and setting it as the default response class (along the lines of what was recommended here). Something like this: def serialize_arbitrary_data(obj: Any) -> Any:
...
class MyJsonResponse(JSONResponse):
def render(self, content: Any) -> bytes:
return json.dumps(content, default=serialize_arbitrary_data) This worked because the behavior with pydantic 1 is to leave unknown types alone when serializing. When I upgraded to pydantic 2, this stopped working, because pydantic tries (and fails) to convert the custom types to json-serializable primitives before my custom response's The same is true for adding encoders to This is a reasonable design decision (to say that all conversion to json-serializable primitives must occur at the pydantic level, and only the JSON serialization itself can be controlled in the response class) but it definitely removed functionality that cannot be achieved in another way (that I have found so far). In case any of the maintainers read this, would you be open to a feature allowing opt-in behavior of calling The next-best workaround I know of is to use the
In the meantime, I have (very redundantly) done the serialization in pydantic by annotating all of the models, using something like this: def serialize_arbitrary_data(obj: Any) -> Any:
...
def recursively_serialize_arbitrary_data(obj: Any) -> Any:
# recursively walk a nested structure, calling serialize_arbitrary_data() on leaves
...
ArbitraryData = Annotated[
Dict[str, Any], PlainSerializer(lambda x: recursively_serialize_arbitrary_data(x))
]
class SomeModel(BaseModel):
regular_field: str
nothing_special_here: int
arbitrary_data: ArbitraryData |
Beta Was this translation helpful? Give feedback.
Thanks for this write up! It did help me to figure out the issue I had.
This could be boiled down to the following test, which passes if I use Pydantic v1 and fails for Pydantic 2:
With Pydantic 2, the value of
e["d"]
is2021-04-20T22:33:44.123456Z
. In my case the models are under my control, so annotating the model solves my issue: