Why Generate Python Classes from JSON?
Working with JSON in Python as plain dicts (data["user"]["name"]) is error-prone and provides no IDE autocompletion. Generating a typed class lets you access data as obj.user.name with autocomplete, type checking, and runtime validation.
There are three main approaches: @dataclass, TypedDict, and Pydantic BaseModel. Each serves a different use case.
JSON Type Mapping to Python
| JSON type | Python type | Notes |
|---|---|---|
| string | str | |
| integer | int | |
| float | float | |
| boolean | bool | JSON true/false → Python True/False |
| null | None / Optional[T] | |
| object | Class / TypedDict / dict | Nested → nested class |
| array | list[T] | Element type inferred |
Approach 1: @dataclass — Zero Dependencies
Dataclasses give you real class instances, attribute access syntax, and automatic __init__ with no third-party dependencies:
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class Address:
city: str
state: str
pincode: str
@dataclass
class User:
id: int
name: str
email: str
active: bool
score: float
address: Address
tags: list[str] = field(default_factory=list)
middle_name: Optional[str] = None
# Instantiate directly
user = User(
id=1, name="Ravi", email="ravi@example.com",
active=True, score=98.5,
address=Address(city="Surat", state="Gujarat", pincode="395001"),
tags=["admin", "developer"]
)
print(user.address.city) # => "Surat"
print(user.tags[0]) # => "admin"Dataclasses do not automatically parse from dicts. Use dacite for dict-to-class conversion:
# pip install dacite
from dacite import from_dict, Config
import json
raw = json.loads(json_string)
user = from_dict(
data_class=User,
data=raw,
config=Config(strict=True), # fail on unknown keys
)
print(user.address.pincode) # => "395001"Approach 2: TypedDict — Type Hints on Plain Dicts
TypedDict is just a type hint — at runtime it is a completely ordinary Python dict with zero overhead. Use it when you want IDE type checking without creating class instances:
from typing import TypedDict, Optional
class Address(TypedDict):
city: str
state: str
pincode: str
class User(TypedDict, total=False):
id: int
name: str
email: str
active: bool
score: float
address: Address
tags: list[str]
middle_name: Optional[str]Usage — the value is still a plain dict at runtime:
import json
from typing import cast
raw = json.loads(json_string)
user = cast(User, raw) # type-only cast — no runtime effect
print(user["name"]) # still dict access syntax
print(user["address"]["city"])
# Mypy/pyright will catch typos like user["nmae"]Use TypedDict for function signatures, return type annotations, and anywhere you want type hints on existing dict data without converting it to class instances.
Approach 3: Pydantic v2 — Runtime Validation + Serialization
Pydantic is the most powerful option. It validates data at parse time, provides detailed error messages on failure, serializes back to JSON, and generates JSON Schema. Used by FastAPI, SQLModel, and thousands of production services:
# pip install pydantic[email]
from pydantic import BaseModel, EmailStr, Field, field_validator
from typing import Optional
import json
class Address(BaseModel):
city: str
state: str
pincode: str = Field(pattern=r"^\d{6}$") # 6-digit pincode
model_config = {"extra": "forbid"} # reject unknown fields
class User(BaseModel):
id: int = Field(gt=0)
name: str = Field(min_length=1, max_length=100)
email: EmailStr # validates email format
active: bool = True # default value
score: float = Field(ge=0, le=100)
address: Address
tags: list[str] = []
middle_name: Optional[str] = None
model_config = {"extra": "forbid"}
@field_validator("name")
@classmethod
def name_must_not_contain_numbers(cls, v: str) -> str:
if any(c.isdigit() for c in v):
raise ValueError("Name cannot contain numbers")
return v.strip()
# Parse and validate from dict
raw = json.loads(json_string)
user = User.model_validate(raw) # raises ValidationError if invalid
print(user.name) # => "Ravi"
print(user.address.city) # => "Surat"
print(type(user.score)) # => <class 'float'>
# Serialize back to JSON
print(user.model_dump_json(indent=2))
print(user.model_dump()) # => dict
# Generate JSON Schema
schema = User.model_json_schema()
print(json.dumps(schema, indent=2)) # ready for OpenAPI
# Safe parse (no exception)
result = User.model_validate(raw, strict=False)When validation fails, Pydantic raises ValidationError with detailed per-field messages:
from pydantic import ValidationError
try:
bad = User.model_validate({"id": -1, "email": "not-an-email"})
except ValidationError as e:
for error in e.errors():
print(f"Field: {error['loc']}, Error: {error['msg']}")
# Field: ('id',), Error: Input should be greater than 0
# Field: ('email',), Error: value is not a valid email address
# Field: ('name',), Error: Field requiredIntegration with FastAPI
Pydantic is FastAPI's native data validation layer — request bodies and response models are Pydantic models:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
app = FastAPI()
class CreateUserRequest(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: EmailStr
role: str = "viewer"
class UserResponse(BaseModel):
id: int
name: str
email: str
role: str
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(req: CreateUserRequest) -> UserResponse:
# FastAPI validates the JSON body automatically using Pydantic
# Returns 422 Unprocessable Entity if validation fails
new_user = db.create_user(req.name, req.email, req.role)
return UserResponse.model_validate(new_user)Which Approach Should You Use?
| Situation | Best choice | Why |
|---|---|---|
| FastAPI request/response models | Pydantic | Native integration, auto-validation |
| External API data ingestion | Pydantic | Catches bad data at the boundary |
| Internal data structures | @dataclass | Clean, typed, zero-dep |
| Type hints on existing dicts | TypedDict | Zero runtime overhead |
| ORM result sets | TypedDict or dataclass | Data is already validated by DB |
| Config files / env vars | Pydantic | Field validators + error messages |
Use JSONKit's JSON to Python tool to generate all three patterns — @dataclass, TypedDict, and Pydantic BaseModel — from any JSON object in seconds. Paste your JSON, choose the output style, copy the code.