JSON to Python Dataclass

Turn a JSON response into Python classes: a dataclass, a TypedDict, or a Pydantic model when you want the data checked at runtime. Field names are converted to snake_case, so userId becomes user_id, and the Pydantic output adds an alias to every renamed field so it still reads the original key.

JSON Input
Generated Code

1 JSON to Python Dataclass example

JSON sample

1
2
3
4
5
6
7
{
  "id": 1,
  "name": "Ana",
  "orders": [
    { "order_id": 1001, "note": "gift" },
    { "order_id": 1002 }
  ]
}

Generated dataclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str
    orders: list["Order"]


@dataclass
class Order:
    order_id: int
    note: str | None = None

2 Choosing between the three styles

TypedDict

Use it when you keep plain dicts and only want mypy to catch typos. It costs nothing at runtime and protects nothing at runtime.

Dataclass

Use it for real objects with attribute access, equality and a readable repr. It still does no validation, because annotations are metadata, not checks.

Pydantic v2

Use Pydantic at an API boundary. It validates and coerces the data as it arrives, and it is the only one of the three that raises an error when the payload is wrong.

Field ordering

Required fields are emitted before optional ones. Python raises a TypeError at class-definition time when a field without a default follows one with a default, so any other order would fail on import.

3 Modern typing syntax

The output uses list[str] and str | None rather than List[str] and Optional[str]. Built-in generics arrived in Python 3.9 and the pipe union in 3.10, so 3.10 is the floor. On 3.7 to 3.9, add from __future__ import annotations at the top of the file and both forms work for dataclasses and TypedDicts, because annotations are no longer evaluated at runtime. Pydantic evaluates them itself, so on 3.9 it also needs the eval_type_backport package.

Every key is converted to snake_case, and keys that cannot be attribute names are repaired too: content-type becomes content_type, and a key that starts with a digit gets a prefix. For Pydantic the tool adds an alias and sets populate_by_name, so the model still reads the original key off the wire. A dataclass or TypedDict has no alias, so map any renamed key yourself before you pass the data in.

How to use the JSON to Python Dataclass tool

  1. Paste a JSON sample into the left panel.
  2. Set Root name for the top-level class.
  3. Choose a style: Dataclass for plain objects, TypedDict for annotated dicts, Pydantic v2 when you want the data validated as it arrives.
  4. Copy Result, or Download to save it as models.py.

How to turn JSON into Python objects

Pydantic v2, the one that validates

# pip install pydantic
from pydantic import BaseModel, Field
import httpx

class Order(BaseModel):
    order_id: int
    total: float
    note: str | None = None

class User(BaseModel):
    model_config = {"populate_by_name": True}

    id: int
    name: str
    content_type: str | None = Field(None, alias="content-type")
    orders: list[Order]

res = httpx.get("https://api.example.com/user/1")
user = User.model_validate(res.json())   # raises ValidationError if the shape is wrong

print(user.orders[0].total)
print(user.model_dump_json(by_alias=True))

model_validate is the step that matters. A dataclass accepts a string where you declared an int, and the failure shows up somewhere else later.

Dataclass and TypedDict, for comparison

from dataclasses import dataclass, asdict
from typing import TypedDict
import json

@dataclass
class Order:
    order_id: int
    total: float
    note: str | None = None

# Nested dataclasses need building by hand: this does not recurse
raw = json.loads('{"order_id": 1, "total": 10.5}')
order = Order(**raw)
print(asdict(order))

# TypedDict keeps the value a plain dict; mypy checks it, nothing else does
class OrderDict(TypedDict, total=False):
    order_id: int
    total: float
    note: str | None

d: OrderDict = {"order_id": 1, "total": 10.5}

Common errors and how to fix them

TypeError: __init__() got an unexpected keyword argument
The JSON has a key your class does not declare. Add it, or filter the dict first. Pydantic ignores extras by default; a dataclass raises.
non-default argument follows default argument
A field without a default is declared after one with a default. The generator sorts required fields first for exactly this reason; reordering by hand reintroduces it.
TypeError: 'type' object is not subscriptable
list[str] and str | None need Python 3.9 and 3.10. On older versions, add from __future__ import annotations at the top of the file.
A key with a hyphen will not map
content-type cannot be an attribute name. The generator renames it to content_type and adds a Pydantic alias; for a dataclass or TypedDict you have to remap it yourself.
A dataclass accepted the wrong type without complaining
Annotations are metadata, not checks. Nothing enforces them at runtime. Use Pydantic, or run mypy in CI.

Good to know

Pydantic v2 rewrote the validation core in Rust and is roughly five to fifty times faster than v1 depending on the workload, which removed most of the performance argument against validating at the boundary. The API changed too: parse_obj became model_validate, .dict() became .model_dump(), and Config became model_config. Code and answers written for v1 will not run on v2.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

Dataclass, TypedDict or Pydantic?

Use TypedDict when you keep plain dicts and only want the type checker's help. Use a dataclass for real objects with attribute access. Use Pydantic when the data should be validated at runtime, which is the right choice for anything arriving over a network.

How are keys that are not valid identifiers handled?

The tool converts every key to snake_case, so content-type becomes content_type and userId becomes user_id, and a key that starts with a digit, such as 2fa, gets an f_ prefix. For Pydantic it adds an alias so the original key still maps. For a dataclass or TypedDict, map the renamed keys yourself.

Which typing syntax does it emit?

The modern one: list[str] and str | None rather than List[str] and Optional[str]. That needs Python 3.10 or later. On 3.7 to 3.9, add from __future__ import annotations at the top of the file; Pydantic on 3.9 also needs the eval_type_backport package.

Why is the field order what it is?

Required fields come first, then the ones with defaults. Python raises a TypeError at class-definition time when a field without a default follows one with a default, so any other order would fail on import.

Does a dataclass check the types at runtime?

No. Annotations are metadata, so passing a str where you declared int works until some later code breaks on it. If you need enforcement, use Pydantic, or run mypy in CI to catch it statically.

Related

The same sample becomes a TypeScript interface or a Go struct. For a contract rather than a class, the JSON Schema generator produces one you can validate against in any language.

More Data Converters

Browse all data converters

Copied to clipboard!