From JSON to Pydantic and TypeScript models
A poorly documented API payload is routine: a webhook or legacy-endpoint JSON shows up and someone has to write the Pydantic model or the TypeScript interface by hand. The tedious part isn't the typing, it's figuring out what the payload actually contains when the examples don't agree with each other.
[
{"id": 1, "zip": "12345", "nickname": "alice", "total": 9.9},
{"id": 2, "zip": 67890, "nickname": null}
]
Two rows, three traps:
zipcomes in as a string in one record and a number in the other. Mixed type: either the source is messy, or these are two versions of the producer. The model has to decide (strwith coercion,Union, or fix the source).nicknameisnullin one record: genuinely optional, notstr.totalonly exists in the first: a missing field is different from a null field, and the model has to reflect that.
Inferring from a single example gets all three wrong. Serious inference looks
at every record and aggregates: which fields show up in all of them, which are
sometimes missing, which change type, what looks like a date
(created_at as ISO in one record and 15/01/2024 in the
other is a warning, not a coincidence).
The result in both worlds
# Pydantic
class Item(BaseModel):
id: int
zip: str | int # mixed type: decide and normalize
nickname: str | None = None
total: float | None = None
// TypeScript
interface Item {
id: number;
zip: string | number;
nickname: string | null;
total?: number;
}
Notice that nickname and total aren't the same
thing: one is "always present, can be null", the other is "might not come at
all". A good model preserves that difference; flattening everything into
optional hides the contract.