Unit 03.02: JSON in, JSON out, and the encoding boundary
JSON has no date type and no decimal type, and both are things you will want to send.
The serialisation boundary
An endpoint returning a date and a Decimal, and what arrives as JSON.
The code shows the types on both sides.
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
from datetime import date
from decimal import Decimal
app = FastAPI()
class Invoice(BaseModel):
id: str
issued: date
amount: Decimal
@app.get("/invoice")
def get_invoice() -> Invoice:
return Invoice(id="INV-1", issued=date(2026, 6, 14), amount=Decimal("240.50"))
client = TestClient(app)
body = client.get("/invoice").json()
print("JSON sent to the client:", body)
for key, value in body.items():
print(f" {key:8} {type(value).__name__}")
print("\ndate became a string; Decimal became a string too, not a float")
# JSON has no date and no decimal type. FastAPI serialises both to strings,
# which preserves precision -- a float would not. The client has to parse them
# back, so the format is part of your contract.
The date becomes a string in ISO format. The Decimal also becomes a string, not a float - which preserves the exact value, because a float cannot represent many decimal amounts precisely.
That choice matters for money. A float round-trip can change 240.50 into something that is not 240.50, and the error appears in a reconciliation weeks later.
The mistake this prevents
The mistake is typing money as float because it looks numeric. Use Decimal and accept the string on the wire - the alternative is a value that is nearly right, which is the worst kind of wrong for money.
Takeaway
JSON has no date or decimal type. FastAPI serialises both to strings; use Decimal for money so precision survives the round trip.
