JSON to Python

Generate Python dataclasses from a JSON sample — Optional fields with defaults, keyword-safe field names, and the dataclass field-order rule applied with every moved field reported. Free, no sign-up — runs locally in your browser.

Your work is auto-saved in this browser only, so a refresh won't lose it. Nothing is uploaded.

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

  1. 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.
  2. Name the root class. Nested classes are named in PascalCase after the keys that hold them.
  3. Press Generate Python. Helper classes come first (dependency order), the root class last — the file runs top to bottom as written.
  4. Read the notes: they list each Any and each optional field that was moved below the required fields to satisfy the dataclass rule.
  5. Copy or download the .py file, then refine: datetime for timestamp strings, Decimal for 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.

Frequently asked questions

Why dataclasses and not TypedDict or plain classes?

Dataclasses are the most useful default the standard library offers: you get construction, equality and a readable repr for free, they work with every JSON decoder's object_hook or a simple unpacking call, and they are what most Python codebases reach for when a payload needs a shape. TypedDict is a fine alternative when you want to keep dicts as dicts — generating it is a small edit away from this output, since the field names and types already line up.

Why did a field move to the bottom of its class?

Because Python requires it. A dataclass field with a default value cannot be followed by a field without one, and a key that is absent from some elements of the sample is optional here — typed Optional[...] and given a default of None. When an optional field originally sat above required ones, the generator moves it below them and says so in the notes, rather than emitting a class Python would refuse to define. Nothing else about the field changes; only the declaration order, which carries no meaning for the JSON.

What does typing.Any in the output mean?

The sample gave no precise type for that slot: the value was null (JSON null carries no type information at all), the slot mixed unrelated types across the sample, or the array was empty so its element type is unknowable. Each Any is listed in the notes with its reason. That is the generator declining to invent a type your real data might violate — the precision you gain by replacing them with hand-chosen types is real, but it has to come from your knowledge of the API, not from a coin flip.

How are Python keywords and odd keys handled?

A key that is a Python keyword or not a legal identifier — "class", "@id", "9lives" — cannot be a field name. The generator appends an underscore or adjusts the spelling into a legal field name and records the original key in a comment directly above the field, so the mapping back to the JSON stays explicit and greppable. Keys are never silently dropped: if it was in the sample, it is in the class, under a name Python accepts.

Does an integer-looking float become int?

JSON itself cannot tell 1 apart from 1.0 — the parser sees the same number — so yes: a slot whose values are all whole numbers is typed int, even if the sender thinks of them as floats. A slot that mixes whole and fractional values widens to float. If a field is semantically a float (a latitude, a ratio), change that one annotation after generating; the generator reports what the data proves, and says so.

Related tools