Why generate Python dataclasses from a sample?
Python will happily hand you a JSON payload as nested dicts —
and then every consumer of that payload is one typo'd key away
from a KeyError three frames deep. Dataclasses
give the payload a declared shape: fields, types, and which
ones may be missing, all readable at a glance and enforced by
your editor's type checker. Writing them by hand from a real
response is transcription work; the sample already states
every fact the classes need. This converter does the
transcription — including the fussy parts, like Python
keywords for keys and the field-ordering rule dataclasses
impose — and reports every judgement call it had to make.
How to use this tool
- Paste the JSON sample. Real responses beat hand-made examples, because the generator can only mark a field optional if the sample actually shows it missing somewhere.
- Name the root class. Nested classes are named in PascalCase after the keys that hold them.
- Press Generate Python. Helper classes come first (dependency order), the root class last — the file runs top to bottom as written.
- Read the notes: they list each
Anyand each optional field that was moved below the required fields to satisfy the dataclass rule. - Copy or download the
.pyfile, then refine:datetimefor timestamp strings,Decimalfor money, enums where a string is really a closed set.
The rules it follows
Naming. Classes PascalCase, fields
snake_case; keys Python cannot use are adjusted and their
originals kept in comments. Types. str, int, float,
bool, list[T], and
Optional[T] when a key is absent from some
sightings or explicitly null beside typed values.
Ordering. Required fields first, optional
fields (defaulted to None) after — with every
move reported in the notes. Honesty. Nulls,
type mixtures and empty arrays become Any, each
explained. Imports. Only what the output
uses: dataclass always, and from
typing exactly the names that appear.
Worked example
For a readings payload where one sensor skipped its battery field:
{
"station": "North Ridge",
"readings": [
{ "sensor_id": "s1", "temp_c": 21.5, "battery": 0.9 },
{ "sensor_id": "s2", "temp_c": 20 }
]
} the converter emits:
from dataclasses import dataclass
from typing import Optional
@dataclass
class Reading:
sensor_id: str
temp_c: float
battery: Optional[float] = None
@dataclass
class Root:
station: str
readings: list[Reading]
Two quiet decisions are visible there: temp_c
widened to float because one reading was
fractional, and battery became optional because
the sample itself shows it missing.