Define the JSON representation of Decimal, UUID and temporal values before returning them from a FastAPI or MCP boundary. Preserve the precision and timezone semantics the client needs, reject unsupported values, and verify serialization before reporting completion. A database write can succeed while response encoding fails; callers then need reconciliation rather than an automatic second write.
Turn Python types into an explicit wire contract
JSON does not have native Decimal, UUID or date types. A response model and encoder choose their representation. For exact decimal quantities, a documented string can preserve scale and avoid conversion through binary floating point. A date can be an ISO calendar string. A timestamp should state its timezone policy. UUIDs commonly travel as strings, but the schema must still distinguish them from arbitrary text.
FastAPI's extra-data-types guidance describes supported Python types and their conversions. Actual output depends on the response model, serialization mode and any custom Response implementation. Inspect the complete boundary used by your endpoint; an encoder applied only to a text field does not necessarily normalize a parallel structured result. Pin Pydantic and FastAPI versions when checking the generated schema.
FastAPI: extra data types and API representations
A strict synthetic serialization policy
Our isolated standard-library fixture chooses decimal strings, UUID strings, ISO dates and timezone-aware datetime strings. It rejects naive timestamps, non-finite Decimal values, floating NaN and an unsupported object. Those are deliberate editorial policy decisions, not claims about every FastAPI default. The fixture ran on Python 3.14.7 and retained the exact wire output.
{
"amount": "12.3400",
"id": "00000000-0000-0000-0000-000000000001",
"day": "2026-11-05",
"at": "2026-11-05T00:00:00+00:00"
}The preserved four decimal places are meaningful only if the domain contract says they are. If amount is actually a measurement with an approximate numeric contract, a JSON number may be appropriate. Choose based on client arithmetic and precision requirements rather than converting every database numeric field the same way. A frontend must not sort decimal strings lexicographically when it intends numeric ordering.
| Input | Chosen representation or outcome |
|---|---|
| Decimal 12.3400 | String preserves scale |
| UUID and date | Explicit string forms |
| Naive datetime | ValueError |
| NaN or unknown object | Rejected |
Avoid a universal string fallback
Converting every unknown object with str can hide an accidental type change. An internal object may become a representation that contains implementation details or varies between versions. Prefer explicit serializers for supported values and a clear failure for everything else. Use allow_nan=False or an equivalent strict setting at the final encoder so non-standard NaN or Infinity cannot escape as apparently valid portable JSON.
Validate the whole response shape before committing a side effect where practical, while keeping the transaction's actual constraints authoritative. For generated IDs or timestamps only known after the write, use tested response models and an idempotent operation identity. If response encoding fails after commit, store enough durable state to answer a status lookup. A 500 response does not prove that the original write was rolled back.
Test text, structured data and clients together
An MCP adapter may return the same object as text and structuredContent. Normalize once through the chosen public representation and build both views from that value. Then compare them in a fixture. Otherwise one path may serialize Decimal as a string and the other as a rounded float, leaving the model and machine consumer with different evidence for the same result.
The next integration check should run the exact endpoint on its pinned framework versions and inspect schema plus response bytes. Include empty collections, null values, nested records and a very large decimal. A successful primitive example does not cover custom response classes or every database codec. The saved fixture provides a reproducible representation policy; production behavior remains a release-specific assertion to verify against real application routing.
LLM structured output validation
Building UltraFit360: the app, coach portal and shared backend
Bring one API or data workflow and a sanitized failure example for a bounded implementation assessment.
How should Decimal, UUID and dates be serialized in FastAPI responses?
Choose explicit documented wire representations, validate timezone and precision rules, and reject unsupported or non-finite values at the final serialization boundary.
Should every Decimal become a float?
Only when the domain permits that precision contract. Exact decimal strings are one option when scale and arithmetic semantics must survive.
Does a serialization error prove a write failed?
No. The write may already be committed, so use durable operation identity and reconciliation before retrying.
