JSON to Python
JSON to Python generates dataclasses, TypedDicts or Pydantic models from a sample document in your browser. The three answer different questions: a dataclass describes the data with no dependencies, a TypedDict describes the dict json.loads already gave you, and a Pydantic model validates it at the boundary.
Empty JSON input (truncated document)
^
How does JSON to Python work?
Pick the shape by what you intend to do with the result. The three are not interchangeable and choosing the wrong one is the most common problem with generated Python.
dataclass, TypedDict, Pydantic
A @dataclass gives you a real object with attribute access, equality and a useful repr, and no third-party dependency — but it does no validation, so a field annotated int will happily hold a string it was constructed with. A TypedDict annotates the plain dictionary json.loads already returned, so nothing has to be constructed at all and the type checker still catches a misspelled key; it is the right answer when you are passing parsed JSON around as-is. A Pydantic BaseModel actually validates and coerces at construction, which is what an API client wants at its boundary, at the cost of a dependency and some runtime work.
Field order is a correctness problem, not a style one
A dataclass field with a default cannot precede one without: Python raises TypeError at import time, not at first use. Because optional fields get = None, the generator emits every required field first. A generator that preserves JSON key order here produces a module that fails to import.
Optional means "can be None", not "can be omitted"
This trips people up in Python specifically, because Optional[int] reads like it means optional and means nullable. In a dataclass, a field missing from some records is emitted as Optional[int] = None — the annotation carries the nullability and the default carries the absence. In a TypedDict the two are genuinely separate, so an absent key is NotRequired[int] and a null value is Optional[int].
Renamed keys keep an alias
Python convention is snake_case, and JSON from a JavaScript service usually is not. Keys are renamed and the original is recorded — as a comment in a dataclass or TypedDict, and as a real Field(alias=…) in Pydantic, where it changes behaviour rather than just documenting it.
Forward references are quoted
Nested classes are emitted before the classes that use them, but the annotation is still written as a string, which keeps the module importable if you reorder it by hand.
Sample
[
{ "userId": 1, "team": "ops" },
{ "userId": 2 }
]Generated
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
user_id: int # JSON key: userId
team: Optional[str] = NoneWhat options and edge cases does JSON to Python support?
| Parameter | Type | Default | Behaviour & edge cases |
|---|---|---|---|
| Shape | dataclass / TypedDict / pydantic | dataclass | dataclass for a dependency-free object; TypedDict to annotate the dict json.loads returned; pydantic when you want the values validated at the boundary. |
| snake_case | boolean | on | Renames keys to Python convention. In pydantic this becomes a real alias, so parsing still matches the wire format; elsewhere it is recorded in a comment and you must map the keys yourself. |
| X | None | boolean | off | Python 3.10+ union syntax instead of Optional[X]. Shorter, and it drops the typing import — but it is a syntax error on 3.9 and below. |
| string | Python type | str | Detected formats are not mapped to datetime or UUID: json.loads returns strings, and annotating one as datetime would be a lie unless something parses it. |
| integer / number | Python type | int / float | Separated by whether any sample had a fractional part. Python integers are arbitrary precision, so there is no int64 distinction to make here. |
| Optional field | annotation | Optional[T] = None | In a TypedDict an absent key is NotRequired[T] instead, which is the distinction Python makes and the other two shapes cannot. |
| Field order | required first | enforced | A dataclass field without a default cannot follow one with a default. Required fields are emitted first regardless of JSON key order. |
| Nested object | class | quoted reference | Promoted to its own class, defined before use, and referenced by a quoted forward reference so the module survives reordering. |
Frequently asked questions
Which should I pick: dataclass, TypedDict or Pydantic?
If the data crosses a trust boundary — an HTTP response, a webhook, a file someone uploaded — use Pydantic, because it is the only one of the three that actually checks anything. If you are annotating dicts you already parsed and passing them around internally, use TypedDict, which costs nothing at runtime. Use a dataclass when you want a real object and no dependency, accepting that the annotations are documentation for your type checker rather than a guarantee.
Why are my fields in a different order from my JSON?
Because a dataclass field with a default cannot come after one without — Python raises TypeError when the module is imported, before any of your code runs. Optional fields get = None, so they are all moved after the required ones. TypedDict and Pydantic have no such rule and keep the original order.
Does Optional[int] mean the key can be missing?
No, and this is the most common misreading in Python typing. Optional[int] means the value may be None. Whether the key may be absent is a separate question, expressed by a default in a dataclass or by NotRequired in a TypedDict. The generator distinguishes them: a key missing from some records gets a default, a value that is null in some records gets Optional.
Why is my timestamp str instead of datetime?
json.loads returns a string, so annotating it datetime would be false unless something parses it. In Pydantic you can change the annotation to datetime and it will parse ISO 8601 for you, which is one of the better reasons to pick Pydantic. In a dataclass or TypedDict, changing the annotation changes nothing at runtime and misleads every reader.
Can I use the generated TypedDict on Python 3.9?
Yes — the import is from typing_extensions rather than typing, which is why it is written that way. NotRequired only landed in the standard library in 3.11, and typing_extensions backports it to every version that has TypedDict at all. Turning on the X | None option, by contrast, does require 3.10.
Is my JSON uploaded?
No. Parsing, inference and rendering all happen in your browser, which you can verify in the Network panel. Nothing about generating a class from a sample requires a server, and the samples people paste are usually real payloads from a system they are integrating against.