JSON to Python Online

Paste JSON and get Python types for it: Pydantic v2 models that validate the data, standard-library dataclasses with a from_dict() helper that builds the nested objects, or TypedDicts that describe the parsed dict for mypy and Pyright. Choose the Python version you target and the syntax follows, from Optional[str] for 3.8 and 3.9 to str | None for 3.10 and later.

The code is generated in your browser as you type; nothing is uploaded. Keys become snake_case attributes, and keys that cannot be Python names, such as class, first-name or 2fa, keep working through an alias. Every item in an array is read, so a key that only some items have becomes optional.

InputPaste, type or drop a .json file
Pythonroot.py
# Python 3.10+, needs Pydantic v2 (pip install "pydantic>=2")

from typing import Any

from pydantic import BaseModel


class Address(BaseModel):
    street: str
    city: str
    state: str
    zip: str
    country: str


class Post(BaseModel):
    id: int
    title: str
    body: str
    published: bool
    tags: list[str]
    created_at: str
    updated_at: str | None = None


class Settings(BaseModel):
    theme: str
    notifications: bool
    language: str


class Root(BaseModel):
    id: int
    name: str
    email: str
    avatar_url: str
    is_active: bool
    created_at: str
    address: Address
    posts: list[Post]
    settings: Settings
    metadata: Any  # only null in the sample
Valid JSON4 classes · 25 fieldsDepth 41.0 KB → 704 BProcessed locally
Send output toText Diff Checker

How to use it

  1. Paste JSON into Input, open or drop a .json file, or load a sample; the code updates as you type.
  2. Pick Pydantic, Dataclass or TypedDict, the Python version you target, and the root class name.
  3. Press Convert (⌘↵), then copy the code or download it as a .py file.

Related tools

JSON to TypeScriptConvertJSON to Go StructConvertJSON Schema ValidatorFormatJSON FormatterFormat

Next: check the same payload against a contract in the JSON Schema Validator, generate matching types for the front end with JSON to TypeScript, or compare two generated versions in the Text Diff Checker.

What this tool does

The converter reads the whole document and builds one class per object shape, defining nested classes before the classes that use them, because Python evaluates annotations when a class is created. All the objects in an array are merged: a key missing from some of them becomes optional (= None in Pydantic and dataclasses, NotRequired in a TypedDict), and a key that is sometimes null becomes X | None. Whole numbers become int, numbers with a decimal point or exponent become float, and a value that is a string in one place and a number in another becomes a union such as str | int. Python ints have no size limit, so even 64-bit IDs need no special type. A field that was only ever null or an empty array is typed Any, with a comment saying so, and an object with no keys becomes dict[str, Any]. With snake_case names on, userId becomes user_id; Pydantic maps it back with Field(alias="userId") and a dataclass's from_dict() reads d["userId"]. Keywords get a trailing underscore (class_), names Pydantic reserves such as json and copy are renamed, and a field never reuses a name like str or list that its own annotations depend on. A TypedDict has to keep the JSON keys exactly, so one with keys that are not identifiers is written in the functional form, TypedDict("Name", {"first-name": str}). The first line of the output says which Python version and packages the code needs.

Example input and output

Pydantic v2 models from camelCase keys (Root type: User)
Input
{"userId": 7, "displayName": "Ada", "class": "admin", "lastLogin": null, "teams": [{"teamId": 1, "name": "core"}]}
Output
# Python 3.10+, needs Pydantic v2 (pip install "pydantic>=2")

from typing import Any

from pydantic import BaseModel, Field


class Team(BaseModel):
    team_id: int = Field(alias="teamId")
    name: str


class User(BaseModel):
    user_id: int = Field(alias="userId")
    display_name: str = Field(alias="displayName")
    class_: str = Field(alias="class")
    last_login: Any = Field(alias="lastLogin")  # only null in the sample
    teams: list[Team]

Validate with User.model_validate_json(text). The aliases are used when reading JSON; dump with model_dump(by_alias=True) to get the original keys back.

Dataclasses with from_dict() (Dataclass, Root type: Order)
Input
{"order_id": "A-1042", "lines": [{"sku": "TSH-S", "qty": 1}, {"sku": "MUG-1", "qty": 2, "gift": true}]}
Output
# Python 3.10+, standard library only

from dataclasses import dataclass
from typing import Any


@dataclass
class Line:
    sku: str
    qty: int
    gift: bool | None = None

    @classmethod
    def from_dict(cls, d: dict[str, Any]) -> "Line":
        return cls(
            sku=d["sku"],
            qty=d["qty"],
            gift=d.get("gift"),
        )


@dataclass
class Order:
    order_id: str
    lines: list[Line]

    @classmethod
    def from_dict(cls, d: dict[str, Any]) -> "Order":
        return cls(
            order_id=d["order_id"],
            lines=[Line.from_dict(v) for v in d["lines"]],
        )

Order.from_dict(json.loads(text)) builds a Line for every item. gift is missing from the first line, so it defaults to None and is read with d.get().

TypedDict with a key that is not an identifier (TypedDict, 3.11+, Root type: Files)
Input
[{"id": 1, "content-type": "text/plain"}, {"id": 2, "content-type": "image/png", "width": 640}]
Output
# Python 3.11+, standard library only

from typing import NotRequired, TypedDict


File = TypedDict(
    "File",
    {
        "id": int,
        "content-type": str,
        "width": NotRequired[int],
    },
)


Files = list[File]

content-type cannot be written as a class attribute, so the functional syntax is used. A TypedDict adds no runtime checks: data: Files = json.loads(text) only tells the type checker what to expect.

Common errors and fixes

ValidationError: Field required [type=missing]
Every object in your sample had this key, so the field is required, but some real data leaves it out. Paste several responses as one JSON array so missing keys are detected, or change the field to X | None = None.
Input should be a valid integer, got a number with a fractional part [type=int_from_float]
The sample only had whole numbers for this field, so it is int. Change it to float, or include a value with a decimal point in the sample; a field that mixes 8 and 19.99 is generated as float.
Field required when you create a model in code, as User(user_id=7)
Pydantic accepts only the alias (userId) by default. Pass the alias, or allow both names with model_config = ConfigDict(validate_by_name=True, validate_by_alias=True) (Pydantic 2.11+; older versions use populate_by_name=True). model_dump() also uses the Python names unless you pass by_alias=True.
TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'
Code written for Python 3.10+ is running on 3.9 or older, where str | None cannot be evaluated at runtime. Choose 3.8–3.9 under Python to get Optional[str], List[int] and Union[...] instead.
ImportError: cannot import name 'NotRequired' from 'typing'
typing.NotRequired was added in Python 3.11. With 3.10 or 3.8–3.9 selected, the TypedDict output imports NotRequired and TypedDict from typing_extensions instead (pip install typing_extensions); importing both from there also keeps the optional keys correct at runtime.
Nested values are still dicts after Order(**data)
A dataclass does not convert anything: unpacking the JSON passes nested objects through as plain dicts, and fails on keys that are not parameter names. Turn on from_dict() and use Order.from_dict(data), which builds every nested dataclass and maps renamed keys.

Common use cases

  • Generating Pydantic models for FastAPI request and response bodies
  • Typing a third-party API response before writing a client
  • Adding TypedDicts so mypy or Pyright can check code that handles json.loads() output
  • Loading a JSON config file into dataclasses without extra dependencies
  • Turning saved webhook payloads into test fixtures with real types

Privacy and security notes

  • Your JSON is converted by JavaScript in this browser tab and is not sent to DevPrix or stored anywhere.
  • The generated classes contain your key names and inferred types, never the values, so they are safe to commit even when the sample was a production response.
  • Share copies a link that carries the JSON after the # in the URL. Browsers do not send that part to servers, but anyone who opens the link can read the input.

Frequently asked questions

Should I choose Pydantic, a dataclass or a TypedDict?

Choose Pydantic when the data comes from outside and you want it validated and converted: wrong types raise a clear error. A dataclass needs no dependency and gives you real objects, but checks nothing at runtime; from_dict() only builds the nested classes. A TypedDict describes the dict that json.loads() returns, for type checkers only, which suits code that passes the data on unchanged.

Which Python version should I pick?

3.11+ writes str | None and imports NotRequired from typing. 3.10 writes the same syntax but takes NotRequired (and TypedDict) from typing_extensions. 3.8–3.9 writes Optional[str], List[int] and Union[...], because the | syntax fails at runtime there, which matters for Pydantic and dataclasses since they evaluate their annotations.

How do I load a JSON array or a plain value?

When the JSON is not an object, the root becomes an alias such as Root = list[RootItem]. With Pydantic, validate it with TypeAdapter(Root).validate_json(text). With dataclasses, build the items yourself: [RootItem.from_dict(v) for v in json.loads(text)].

Why are dates typed as str?

JSON has no date type, and APIs write dates in many formats, so they stay strings. In a Pydantic model you can change the annotation to datetime or date and Pydantic will parse ISO 8601 strings for you; dataclasses and TypedDicts would need you to convert them yourself.

Do large integers need a special type?

No. Python's int has no fixed size, and json.loads() reads integers exactly, so 9007199254740993 or even a 30-digit number fits in an int field. Only floats are limited, to about 15–17 significant digits.