— The stack

Decimal, UUID and dates in FastAPI responses: define the JSON contract

Define precise JSON contracts for Decimal, UUID, dates and timestamps, with an executed strict serializer fixture and post-commit failure handling.

3 min readUpdated 2026-11-05By AptixLabs studio
Python values: Decimal, UUID and time; Representation policy: Precision and timezone; Strict encoder: Reject unsupported values; Client contract: Validate returned shapeAptixLabs · 2026-11-05

Original explanatory figure of a synthetic or proposed design; not a deployed product screenshot. View full-size diagram

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.

Actual local strict-policy fixture results
InputChosen representation or outcome
Decimal 12.3400String preserves scale
UUID and dateExplicit string forms
Naive datetimeValueError
NaN or unknown objectRejected

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.

AptixLabs saas development

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.

Need help with b2b saas development for connected customer workflows?

Tell us about your product, existing stack and the problem you need to solve. We can discuss integration scope, implementation tradeoffs and a practical first milestone.

Start a conversation