Why Convert JSON to a Python Class?
Python can load JSON into a dict with one line, but then every access becomes data["user"]["profile"]["bio"]and your IDE can't help with autocomplete or typo detection. A class gives you dot access, type hints, equality, and (with Pydantic) validation. For API clients, data pipelines, and any Python 3.7+ project, typed classes are standard practice — generating them from a real JSON sample saves tedious hand-typing.
Example: Dataclass Output
Given this JSON:
{
"id": 42,
"name": "Ada Lovelace",
"email": "[email protected]",
"verified": true,
"roles": ["admin", "editor"],
"profile": {
"bio": "Mathematician",
"avatar": null,
"website": "https://ada.dev"
}
}The @dataclass output:
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class Profile:
bio: str
avatar: Optional[str]
website: str
@dataclass
class User:
id: int
name: str
email: str
verified: bool
roles: List[str]
profile: Profile
# Load:
def from_dict(cls, data):
import dataclasses, inspect
kwargs = {}
for f in dataclasses.fields(cls):
v = data.get(f.name)
if dataclasses.is_dataclass(f.type) and isinstance(v, dict):
v = from_dict(f.type, v)
kwargs[f.name] = v
return cls(**kwargs)
user = from_dict(User, data)Example: Pydantic Output
from pydantic import BaseModel
from typing import List, Optional
class Profile(BaseModel):
bio: str
avatar: Optional[str] = None
website: str
class User(BaseModel):
id: int
name: str
email: str
verified: bool
roles: List[str]
profile: Profile
# Load (Pydantic v2):
user = User.model_validate(data)
# Serialise back:
json_str = user.model_dump_json()Dataclass vs Pydantic vs attrs vs msgspec
@dataclass— standard library, zero cost. No validation at runtime; type hints are hints only. Pick this for internal data classes and when you trust the input.- Pydantic v2 — the de facto standard for API models. Validates, coerces, and serialises automatically. 10× faster than Pydantic v1 (Rust-backed core). Pairs natively with FastAPI.
- attrs — predates dataclass and still has features it lacks (slots, converters, validators). Pick if you need the extras.
- msgspec— "Pydantic but 50× faster and no validation". Optimised for serialising/deserialising large payloads. Pick when raw throughput matters and the data is already clean.
Type Mapping Reference
"abc"→str42→int3.14→floattrue/false→boolnull→Optional[X]orX | None(3.10+)[1, 2]→List[int]orlist[int](3.9+){...}→ nested class- Mixed array →
List[Union[A, B]]or justList[Any]
Handling Dates, Decimals, and Enums
JSON timestamps come as strings. In Pydantic, type the field as datetime and Pydantic parses ISO-8601 automatically. In a dataclass, keep it as str and call datetime.fromisoformat() at use sites, or write a __post_init__ that converts the string in place.
For money amounts always use decimal.Decimal rather than float — floats lose precision on 0.1 + 0.2 and will silently corrupt accounting data. Pydantic has a condecimal helper for validation (max/min digits, decimal places).
Enums (fields with a known fixed set of values) can be typed as Literal["admin", "editor"] in both dataclasses and Pydantic. Pydantic will raise a clear error if an unknown value arrives.
Custom Validators (Pydantic)
from pydantic import BaseModel, field_validator, EmailStr
class User(BaseModel):
id: int
name: str
email: EmailStr # validates format
roles: list[str]
@field_validator("roles")
@classmethod
def not_empty(cls, v):
if not v:
raise ValueError("User must have at least one role")
return v
# User(id=1, name="Ada", email="bad")
# → ValidationError: email is not a valid emailCommon Pitfalls
- Mutable default values. In a dataclass,
roles: List[str] = []is a bug — the list is shared across instances. Useroles: List[str] = field(default_factory=list). Pydantic handles this correctly by default. - Reserved keywords. JSON often contains fields like
type,class,fromwhich clash with Python keywords. In Pydantic useField(alias="type")and name the attributetype_. For dataclasses, rename the attribute and handle translation infrom_dict. - Big integers. Python handles arbitrary-precision integers natively — no overflow worries like Go or JavaScript.
- Unknown fields. Pydantic v2 ignores unknown JSON keys by default. Set
model_config = ConfigDict(extra="forbid")to raise an error — useful during API development to catch backend changes early.