How to directly serialize Decimals to JSON numbers? (FHIR spec decimal cmpliance) #14929
Replies: 3 comments 2 replies
-
|
The root issue: FastAPI calls Solution: Return a import simplejson
from fastapi import Response
class FHIRResponse(Response):
media_type = "application/fhir+json"
def render(self, content) -> bytes:
return simplejson.dumps(content, use_decimal=True).encode("utf-8")
@app.get("/observation", response_class=FHIRResponse)
async def get_observation():
data = {"valueDecimal": Decimal("3.5000")}
return FHIRResponse(content=data) # Return directly, not just the dictBy returning Alternative with import orjson
class FHIRResponse(Response):
def render(self, content) -> bytes:
return orjson.dumps(content, option=orjson.OPT_PASSTHROUGH_DECIMAL,
default=lambda x: float(x) if isinstance(x, Decimal) else x)The key insight: |
Beta Was this translation helpful? Give feedback.
-
|
Since FastAPI 0.130.0, from decimal import Decimal
from fastapi import FastAPI
from fastapi.routing import APIRouter
from pydantic import BaseModel, field_serializer
app = FastAPI()
router = APIRouter()
class Foo(BaseModel):
value: Decimal
@field_serializer("value", mode="plain")
def serialize_value(self, value: Decimal) -> float:
return float(value)
@router.post("/")
async def foo() -> Foo:
return Foo(value=Decimal("0.01"))
app.include_router(router)Or, using custom type with serializer: from decimal import Decimal
from typing import Annotated, TypeAlias
from fastapi import FastAPI
from fastapi.routing import APIRouter
from pydantic import BaseModel, PlainSerializer
app = FastAPI()
router = APIRouter()
MyDecimal: TypeAlias = Annotated[Decimal, PlainSerializer(lambda v: float(v))]
class Foo(BaseModel):
value: MyDecimal
@router.post("/")
async def foo() -> Foo:
return Foo(value=Decimal("0.01"))
app.include_router(router)BTW, could you please explain why we may need to serialize |
Beta Was this translation helpful? Give feedback.
-
|
Hi @YuriiMotov and @nishaalnaseer, To answer the "why": In healthcare standards like FHIR (Fast Healthcare Interoperability Resources), the number of decimal places represents the precision of the measurement. For example, in a lab result, 3.5 and 3.500 are technically different from a clinical governance perspective. 3.500 implies a higher degree of precision in the equipment used. If we serialize Decimal("3.500") as a float or a standard JSON number, most encoders will truncate it to 3.5, losing the metadata of the measurement's precision. The Solution for FastAPI 0.130.0+: The challenge is that standard JSON numbers in many libraries don't support trailing zeros. If the FHIR server requires them, the standard practice is indeed using simplejson or keeping it as a strictly formatted string if the receiver expects a JSON number primitive. However, if you want to keep the Unit of Work clean while using Pydantic, the best way to handle this without letting jsonable_encoder interfere is using a custom Response class that overrides the default encoder entirely, or using Response directly as mentioned by @Toygarmetu. If you want to maintain the response_model benefits but ensure the output is compliant, you can use a custom encoder at the app level: Python class HighPrecisionJSONResponse(JSONResponse): @app.get("/observation", response_class=HighPrecisionJSONResponse) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
First Check
Commit to Help
Example Code
Output:
After reading the webpages #5023 and https://fastapi.tiangolo.com/advanced/custom-response/#custom-response-class I proceeded to configure simplejson into my backend
Curl:
With console log as
First of all I'd like to say deserialization is not the issue as there within the function the type is being seen as a Decimal.
However when the object is being returned Decimal is being turned into a string somewhere in the middle before simplejson gets to serialize the Decimal.
There is a fix which is to return the JSONResponse directly.
But how do i override the part where fastapi turns Decimal into a string?
Beta Was this translation helpful? Give feedback.
All reactions