Skip to content

JSON to Python

Generate Python dataclasses or TypedDicts from a JSON sample.

Input

What this tool does

Generate Python dataclasses with type annotations from a JSON sample. Each nested object becomes its own dataclass, nullable fields use Optional[T], and the output includes a from-dict converter so you can json.loads() and pass straight in. Powered by quicktype, runs entirely in your browser.

How to use it

Paste JSON (or load the example) and read the Python dataclasses on the right. Each nested object becomes its own dataclass with type annotations; the output is Python 3.7+ compatible.

Input: {"id":42,"name":"devsmiths","createdAt":"2024-03-11T08:24:00Z","stars":1280,"public":true,"contributors":[{"login":"ada","commits":51,"admin":true},{"login":"linus","commits":33,"admin":false}],"homepage":null}

Output (Python):

from dataclasses import dataclass
from typing import Any, List, Optional


@dataclass
class Contributor:
    login: str
    commits: int
    admin: bool


@dataclass
class Root:
    id: int
    name: str
    created_at: str
    stars: int
    public: bool
    contributors: List[Contributor]
    homepage: Optional[Any]

Limits and edge cases

  • Output is @dataclass with type annotations, not Pydantic BaseModel or TypedDict. Dataclasses are stdlib (Python 3.7+) and runtime-free; if you want validation, wrap the dataclass output with Pydantic via @dataclasses.dataclass + pydantic.dataclasses.dataclass, or rewrite by hand.
  • Field names are converted to snake_case from camelCase JSON keys (createdAtcreated_at). This breaks json.loads() + **dict construction — use the generated from_dict converter or cattrs to handle the renaming.
  • Nullable fields use Optional[T] (which is Union[T, None]), not the PEP 604 T | None syntax. Run the output through pyupgrade if you’re on Python 3.10+ and prefer the new syntax.
  • Reserved words and dunder names in JSON keys are escaped with a trailing underscore (class_, type_), with the original spelling preserved in a __post_init__ rename map.
  • Date / datetime strings stay as str; the generator can’t infer ISO 8601 vs slug. Convert via datetime.fromisoformat() in a post-init hook or with cattrs hooks.
  • Single-sample inference limitation applies: keys absent from the sample don’t become Optional automatically.

Frequently asked questions

Why dataclasses and not Pydantic BaseModel?
Dataclasses are stdlib (no runtime dep) and the output works in any Python 3.7+ project. Pydantic is great for runtime validation but adds a ~5 MB dependency tree and changes the semantics (Pydantic coerces types, dataclasses don't). To upgrade: wrap each `@dataclass` with `pydantic.dataclasses.dataclass` — same field syntax, Pydantic validation on construction.
How do I parse a JSON string into the generated dataclasses?
Use the from-dict converter the generator emits: `Root.from_dict(json.loads(payload))`. Pure stdlib `json.loads()` returns a `dict`, not a `Root`. For nested objects, the from-dict walks the tree. If you prefer cattrs (more configurable), the dataclass shapes are compatible — pass them to `cattrs.structure(payload_dict, Root)`.
Why is my JSON key createdAt renamed to created_at?
PEP 8 says Python variable names should be snake_case. The generator follows PEP 8 for the Python side, but JSON serialization needs to know the original key. The from-dict converter handles the rename; if you use a different deserializer (Pydantic, marshmallow), configure it with `populate_by_name = True` and `alias = 'createdAt'`, or convert the dict keys before passing in.
Can I get PEP 604 union syntax (X | None) instead of Optional[X]?
Not from the generator directly. Run the output through `pyupgrade --py310-plus` to convert `Optional[X]` to `X | None`. The semantics are identical; PEP 604 is just a cleaner syntax that requires Python 3.10+.
What about Python keywords as JSON keys (class, def, type)?
They're renamed with a trailing underscore (`class_`, `def_`, `type_`) and the original key is preserved in a `__post_init__` rename map for from-dict deserialization. The rename map is the cleanest way to handle this in Python — alternatives (using `__getattr__` for the original name) work but add runtime cost.
Why doesn't the generator handle decimal.Decimal for money fields?
Because the JSON spec has only one number type and no way to mark a value as 'this should stay decimal'. Convert by hand for money fields, or use a JSON parser that supports lossless number preservation (`simplejson` with `use_decimal=True`) and adjust the dataclass field type to `Decimal`. The generator stays float-only by default.

Content reviewed by