Code Generators
JSON to Pydantic Model Generator
Paste a JSON sample, get clean Pydantic BaseModel classes — nested models named from your keys, Optional[...] where the sample had null, no positional-argument helper code to fight.
In short
Generate Pydantic BaseModel classes from a JSON sample: nested models, List[...] fields, Optional[...] for nulls — quicktype-powered, client-side, models only by default.
By NaveenKumar T · Updated
- BaseModel classes, nested models named from keys
- Optional[...] where the sample had null
- List[...] for arrays of objects
Runs entirely in your browser — nothing you paste or open here is uploaded, logged, or stored.How we handle data →
Example
JSON to Pydantic: input and output
A FastAPI-style order request: a nested customer object, an array of line-item objects, a null coupon field and an ISO timestamp — the shapes that decide which fields become Optional and which become List[Item].
{
"order_id": "ORD-10428",
"customer": { "name": "Ada Lovelace", "email": "ada@example.com" },
"items": [
{ "sku": "TS-BLK-M", "qty": 2, "price": 18.0 },
{ "sku": "MUG-01", "qty": 1, "price": 12.5 }
],
"coupon": null,
"placed_at": "2026-09-18T09:30:00Z"
}from pydantic import BaseModel
from typing import List
from datetime import datetime
class Customer(BaseModel):
name: str
email: str
class Item(BaseModel):
sku: str
qty: int
price: float
class Root(BaseModel):
order_id: str
customer: Customer
items: List[Item]
coupon: None
placed_at: datetime
items became List[Item] with its own nested Item(BaseModel), customer became its own Customer(BaseModel), coupon (sampled only as null) came back typed None, and placed_at — a full ISO date-time string — was typed datetime, matching Pydantic's own built-in datetime-string parsing.
Learn more
From JSON to BaseModel
Inference follows the same shape-from-values approach as every generator on this site: a JSON object becomes a BaseModel subclass, each key becomes a typed field, and a nested object becomes its own BaseModel subclass named from the key it was found under — Customer, Item — referenced by name from the parent model exactly the way you'd nest them by hand. Field NAMES are converted to Python's snake_case convention by default, which is standard Pythonic style but has one honesty caveat covered in the FAQ above: without an explicit alias, the model's field name has to match the incoming JSON key for automatic parsing to actually populate it.
By default this page generates "Models only" — plain BaseModel classes and nothing else — rather than also including the from_dict/to_dict-style helper functions quicktype can optionally add. That default is deliberate: verified against the real generator output, quicktype's helper mode constructs the model with POSITIONAL arguments (Root(order_id, customer, items, ...)), and pydantic.BaseModel's constructor only accepts KEYWORD arguments — so the helper code that quicktype would otherwise generate does not actually run against a real Pydantic model. Models-only avoids shipping code that looks correct and fails the moment you call it; use Pydantic's own Root.model_validate(data) (v2) or Root.parse_obj(data) (v1) instead, which is what the usage example below shows.
| JSON value | Generated type hint |
|---|---|
| Object | A nested BaseModel class |
| Array of objects | List[Item] |
| Array of scalars | List[str] / List[int] / … |
| String | str |
| Integer | int |
| Decimal number | float |
| Boolean | bool |
| null only (every sample) | None |
| null in one sample, a value in another | Optional[str] (or the matching type) |
Pydantic models vs plain classes vs dataclasses
Three ways to give a JSON payload a Python type, in roughly increasing order of built-in behavior: a plain class with type hints and hand-written (or quicktype-generated, via JSON to Python) from_dict/to_dict methods does exactly what you tell it and nothing more; a @dataclass gets a generated __init__, __repr__ and __eq__ for free from the standard library but still performs zero validation on construction; a pydantic.BaseModel validates every field the moment you construct it, coercing where it reasonably can (a numeric string into an int, for instance) and raising a detailed ValidationError where it can't. That validation-on-construction is the entire reason to reach for Pydantic over a plain class or dataclass: the model itself becomes the place where "is this data actually shaped right" gets answered, instead of that check being scattered across the code that happens to consume the object.
| Plain class (JSON to Python) | @dataclass | Pydantic BaseModel (this page) | |
|---|---|---|---|
| Validates on construction | Only if from_dict asserts types | No | Yes |
| Needs a dependency | No | No (standard library) | Yes — pydantic |
| Auto __init__/__repr__/__eq__ | Hand-written | Yes, generated | Yes, generated |
| FastAPI request/response integration | No | No | Yes, natively |
Using the models with FastAPI and requests
The most common home for a generated Pydantic model is a FastAPI request or response type, where the framework itself calls the validation for you on every request; the second most common is validating a response from an outbound call with the requests or httpx library. Both usages are shown below for Pydantic v2's API; if your project is still on v1, swap model_validate for parse_obj and model_dump for dict.
from fastapi import FastAPI
import requests
from models import Root # the generated file
app = FastAPI()
@app.post("/orders")
def create_order(order: Root):
# FastAPI already validated + parsed the body into `order` here.
return {"received": order.model_dump()}
# Validating an outbound API response the same way:
response = requests.get("https://api.example.com/orders/ORD-10428")
order = Root.model_validate(response.json()) # v1: Root.parse_obj(...)
print(order.customer.email)Help
Frequently asked questions
Everything you need to know about the JSON to Pydantic.
01Pydantic v1 or v2 — which does the output target?
Neither exclusively — the default "Models only" output is plain BaseModel subclasses with ordinary Python type hints (str, int, Optional[str], List[Item], nested classes), and nothing about that syntax is v1- or v2-specific: no model_config, no v2-only ConfigDict, no v1-only class Config. That means the generated file runs unchanged on either major version. Where the versions actually differ is how YOU use the models afterward: Pydantic v2 parses a dict with Root.model_validate(data) and serializes with .model_dump(), while v1 uses Root.parse_obj(data) and .dict() — both shown in the usage example below, since which one applies depends on what's installed in your project, not on anything this generator controls.
02How do nulls become Optional[...] and what about missing keys?
A field whose only sampled value is null is typed as plain None (Python's None type-hint, meaning the field can only ever legally hold None) — technically correct for that one sample but not very useful. Paste a second document where the field carries a real value and it widens to Optional[str] (or whichever type fits), which is what you actually want for a genuinely nullable field. A key that's present in one sample and absent from another gets the same Optional[...] treatment, since Pydantic (like the rest of Python's type-hint ecosystem) doesn't distinguish "missing" from "present but None" at the type-hint level the way TypeScript's optional (?) syntax does — both need Optional to be a correctly typed model.
03Are field names converted to snake_case, and do aliases keep the JSON names?
Field names are Pythonized to snake_case by default (orderId in the JSON sample becomes order_id on the model) — that part matches normal Python style. What the generator does NOT add is a Pydantic Field(alias=...) mapping back to the original JSON key, so if your real payload's key doesn't already match the generated Python name, parsing the raw dict directly (Root(**payload) or Root.model_validate(payload)) will not populate that field — Pydantic looks for the field name (or an explicit alias) in the input, and there isn't one here. For a payload with camelCase or otherwise non-Python key names, add Field(alias="orderId") to the affected fields yourself, and set model_config = ConfigDict(populate_by_name=True) (v2) or class Config: allow_population_by_field_name = True (v1) if you also want to construct instances using the Python attribute name directly.
04Why are there no validators or Field constraints?
Because a single sample value carries no information about a CONSTRAINT — "ada@example.com" tells the generator the field is a string, not that every value in production must be a valid email address, and "36" tells it the field is an int, not that age must be non-negative. Add Field(gt=0), Field(max_length=140), an EmailStr type from pydantic (swapping in for str), or a full @field_validator by hand once you know the real business rules; a sample-driven generator has no way to infer them safely. If your source of truth is a specification that DOES state its constraints — an OpenAPI schema, a hand-written JSON Schema — validate against that directly with [JSON Schema Validator](/json-schema-validator) instead of trying to encode every rule into the Pydantic model by hand.
05How is this different from JSON to Python?
[JSON to Python](/json-to-python) generates plain classes with type hints and explicit from_dict/to_dict staticmethods that manually walk the object and assert each field's type as they go — no third-party dependency, works with the standard library alone. This page generates pydantic.BaseModel subclasses instead: validation happens automatically on construction (Root(**data) raises a detailed ValidationError on a bad shape, rather than your code needing to call a separate from_dict helper), and the models integrate directly with FastAPI, which reads BaseModel type hints to generate request/response validation and OpenAPI docs for free. Pick plain classes when you want zero dependencies; pick Pydantic when your project already depends on it (FastAPI virtually guarantees that) or you want validation built into construction itself.