Conversion guide
JSON to Pydantic: Generate Python Models
Generate Pydantic v2 models from a JSON sample for runtime validation in Python. Handles nested objects, optional fields, and Optional vs Union types.
Python services that consume JSON — FastAPI handlers, Celery message workers, anything that touches an external API — need a strict parser at the boundary. Pydantic is the default choice, but writing a model for a 100-field payload by hand is tedious and error-prone. Generating one from a real sample is faster and lines up exactly with the wire format.
Paste a sample into the JSON to Pydantic generator for instant output, or read on for the structural rules.
TL;DR
A JSON to Pydantic converter maps each JSON value to a typed field on a
BaseModel: objects become nested models, arrays become list[T], primitives
map directly. Multiple samples widen fields to Optional[T] = None where the
field can be missing. Pydantic v2 syntax — model_config, Field, and
Annotated — is the default.
Step 1 — Flat primitives
{
"id": "u_001",
"name": "Otter",
"email": "otter@example.com",
"is_admin": false
}
from pydantic import BaseModel
class User(BaseModel):
id: str
name: str
email: str
is_admin: bool
Pydantic preserves the JSON snake_case keys verbatim. If your Python style
demands camelCase or aliases, configure that with Field(alias=...):
from pydantic import BaseModel, ConfigDict, Field
class User(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: str
name: str
email: str
is_admin: bool = Field(alias="isAdmin")
Step 2 — Nested models
Nested JSON objects become nested classes. Extract them by name so they can be referenced elsewhere.
{
"id": "ord_A100",
"customer": {
"name": "Company A",
"country": "US"
},
"shipping": {
"city": "Springfield",
"zip": "00000"
}
}
from pydantic import BaseModel
class Customer(BaseModel):
name: str
country: str
class Shipping(BaseModel):
city: str
zip: str
class Order(BaseModel):
id: str
customer: Customer
shipping: Shipping
Order matters: nested types must be declared before the model that uses them. The generator emits them in that order automatically.
Step 3 — Arrays
Arrays map to list[T]. Mixed-element arrays widen to a union.
{
"tags": ["new", "featured"],
"items": [
{ "sku": "widget", "qty": 2 },
{ "sku": "gadget", "qty": 5 }
]
}
from pydantic import BaseModel
class Item(BaseModel):
sku: str
qty: int
class Payload(BaseModel):
tags: list[str]
items: list[Item]
For a union of primitives — ["a", 1, true] — the field becomes
list[str | int | bool]. Most of the time that's a smell in the source data,
not a real requirement.
Step 4 — Optional and nullable
Pydantic distinguishes "field can be missing" from "field is present but can be
None". The first uses a default; the second uses Optional (or T | None).
Input (multiple samples)
[
{ "id": "u_001", "email": "otter@example.com" },
{ "id": "u_002", "email": null },
{ "id": "u_003", "email": "lynx@example.com", "verified_at": "2026-01-15" }
]
Output
from pydantic import BaseModel
class User(BaseModel):
id: str
email: str | None
verified_at: str | None = None
email is required but can be None. verified_at can be absent — Python sees
None if not supplied. With one sample only, every field is required and
non-nullable; feed several samples whenever possible.
Step 5 — Validate at the boundary
The point of generating a Pydantic model is to actually use it for parsing. Two patterns:
# Throws ValidationError on any mismatch.
user = User.model_validate(payload_dict)
# JSON in, model out — handy in FastAPI handlers.
user = User.model_validate_json(raw_bytes)
For FastAPI, declaring the model as a handler parameter does the parsing automatically and returns a 422 with a structured error list when it fails:
@app.post("/users")
def create_user(user: User) -> User:
return user
For non-Python consumers, generate a matching JSON Schema so other services can validate the same payload.
Common conversion problems
"Pydantic says 'Input should be a valid string' but the value is a number"
The JSON had "price": 19.99 and your generated model declared price: str.
Pydantic v2 is strict about types by default — it does not coerce silently.
Either fix the type or annotate the field with coerce_numbers_to_str=True in
the config.
"Optional field is required at runtime"
x: int | None declares the type as nullable but the field as required. To
make it optional, add a default: x: int | None = None.
"Datetime strings won't parse"
Use datetime from the standard library — Pydantic v2 parses ISO 8601 strings
into datetime automatically when the field type is datetime. The generator
emits str from a JSON sample because it can't know the field is a timestamp;
switch the type after generation.
"Extra fields appear in my output JSON"
Pydantic ignores unknown keys on input by default, but doesn't drop fields when
re-serializing. Configure model_config = ConfigDict(extra="forbid") to reject
unknown keys at the boundary.
"I want one model to accept multiple shapes"
Use a discriminated union with Field(discriminator='type'). The generator
emits a single model — you'll need to refactor it into a union by hand if the
API actually has variants.
Generate in the browser, no upload
The free JSON to Pydantic generator on JSONZen emits a complete Pydantic v2 model with nested classes, optional fields, and proper types. It runs entirely in your browser and never sends your data anywhere.
For TypeScript consumers of the same API, generate a matching TypeScript interface from the same sample so both sides stay in sync.
Closing recommendation
Generate the Pydantic model from a real sample, paste it into your service, and
call model_validate_json at every boundary. The strictness of Pydantic v2
catches API drift the moment it ships — and is friendlier than discovering the
issue in a downstream KeyError six functions later.
Related guides
- JSON to TypeScript: Generate Interfaces
Generate accurate TypeScript interfaces from a JSON sample. Handles nested objects, unions, optional fields, and nullable values without hand-typing.
- JSON Schema vs TypeScript Types: When to Use Which
JSON Schema vs TypeScript types compared on runtime vs compile-time, portability, validation power, and ecosystem. With concrete examples and recommendations.