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
{"userId": 7, "displayName": "Ada", "class": "admin", "lastLogin": null, "teams": [{"teamId": 1, "name": "core"}]}# 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.
{"order_id": "A-1042", "lines": [{"sku": "TSH-S", "qty": 1}, {"sku": "MUG-1", "qty": 2, "gift": true}]}# 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().
[{"id": 1, "content-type": "text/plain"}, {"id": 2, "content-type": "image/png", "width": 640}]# 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.