Example
{
"id": "a3f1c2d4-5b6e-4f70-8a9b-0c1d2e3f4a5b",
"displayName": "Noor",
"createdAt": "2026-02-01T12:00:00Z",
"homepage": "https://noor.dev",
"settings": {
"theme": "dark",
"beta": false
},
"sessions": [
{
"device": "laptop",
"lastSeen": "2026-10-20T08:15:00Z"
},
{
"device": "phone",
"lastSeen": null,
"pushToken": "tok_1"
}
]
}
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
class Settings(BaseModel):
theme: str
beta: bool
class Session(BaseModel):
model_config = ConfigDict(populate_by_name=True)
device: str
last_seen: datetime | None = Field(alias="lastSeen")
push_token: str | None = Field(default=None, alias="pushToken")
class Account(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: UUID
display_name: str = Field(alias="displayName")
created_at: datetime = Field(alias="createdAt")
homepage: HttpUrl
settings: Settings
sessions: list[Session]
Nested models are defined before the models that reference them, so the file runs as-is. pushToken
exists in only one session, so it's optional with a default of None. lastSeen is
always present but sometimes null, so it's nullable but still required.
Using the model
Validating an API response
import httpx
from pydantic import ValidationError
resp = httpx.get("https://api.example.com/me")
try:
account = Account.model_validate(resp.json())
except ValidationError as e:
print(e.errors()) # field-by-field details
else:
print(account.display_name, account.created_at.year)
# Serialize using the original JSON keys
account.model_dump(by_alias=True, mode="json")
As a FastAPI request and response body
from fastapi import FastAPI
app = FastAPI()
@app.post("/accounts")
def create_account(account: Account) -> Account:
# FastAPI validates the request body against the model
return account
Type mapping
| JSON value | Python / Pydantic type |
|---|---|
| string | str |
| ISO 8601 date-time / date | datetime / date |
| UUID string | UUID |
| http(s) URL | HttpUrl |
EmailStr (needs pydantic[email]) | |
| integer / decimal | int / float |
| true / false | bool |
| object | nested BaseModel |
| array | list[T] |
| mixed types | int | str |
Turn off Detect formats to keep every string as plain str. Pick the
List[str], Optional style if you need to support Python 3.8 or 3.9.
Field names that need care
- Python keywords such as
classorfrombecomeclass_andfrom_, with an alias. - Pydantic treats names starting with
_as private, so_idbecomesidwith an alias. - Keys with spaces or dashes are converted to valid identifiers with an alias.
Building the frontend too? Generate TypeScript interfaces or a Zod schema from the same payload.
Frequently asked questions
How do I generate a Pydantic model from JSON?
Paste a JSON sample into the tool above. It creates a BaseModel subclass for every object, infers int, float, bool, str, list and nested model types, detects datetime, UUID and URL strings, and marks fields that are missing or null in some samples as optional.
Is the output Pydantic v1 or v2?
Pydantic v2: it uses model_config = ConfigDict(...) and RootModel. For older projects the field definitions mostly work in v1 too, but replace ConfigDict with an inner class Config and RootModel with __root__.
How do I keep camelCase JSON keys but use snake_case in Python?
Enable "snake_case fields". Each field is renamed to snake_case and gets Field(alias="originalKey"), so validation reads the camelCase key. populate_by_name=True also lets you construct the model with Python names. Use model_dump(by_alias=True) to write the original keys back.
What is the difference between Optional fields and nullable fields?
A key missing from some samples becomes x: T | None = None, so it is not required. A key that is always present but sometimes null becomes x: T | None with no default, so it must be present but may be None.
Why does a field use Any?
Its value was null in every sample, or it was an empty list, so the real type can’t be inferred. Replace Any with the correct type once you know it.