paste PostgreSQL or JSON, get your schema reviewed like a senior would

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:

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. The more records you feed it, the closer the inferred model is to the real contract; one payload can't tell you which fields come and go.

Numbers widen, and that's correct

JSON has one number type, but samples tell you more: {"total": 10} in one record and {"total": 9.9} in another means the field is fractional and the first record just happened to land on a whole value. The inferrer widens int to float the moment any record shows a decimal, because the reverse guess (typing it int from one lucky sample) produces a model that rejects real traffic next Tuesday. The same logic applies to str | int unions: the union is the honest reading of the evidence; narrowing it back down is a decision you make about the source, not something to infer away.

Date strings are a convention, not a type

Nothing in JSON says "2024-01-15T10:30:00Z" is a datetime; it's a string that matches a convention. Inference can recognize ISO 8601 with high confidence and map it to datetime/Date. But when the same field shows "2024-01-15T10:30:00Z" in one record and "15/01/2024" in another, that's not a formatting choice, it's two producers (or one buggy one), and it deserves a warning rather than a silent str. Ambiguous formats like 01/02/2024 can't even be parsed without knowing the locale. The safe model types the field as a string and flags it; the fix belongs at the source.

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. The nullable-vs-optional distinction has its own article, including how to handle the PATCH case where the difference carries meaning.

Nested objects become their own types (an address object becomes an Address model referenced by the parent), and arrays infer from their elements. For streaming logs and exports, NDJSON (one JSON object per line) works the same way, every line is one more sample strengthening the inference.

Schemint's Payload X-Ray does this inference over an object, an array or NDJSON: it shows the shape, the mixed types, the optional fields and generates the Pydantic and TypeScript ready to use. The payload is processed and discarded, nothing is stored. Try it.

Related