
JSON to Python Dataclass Converter
Paste JSON to generate Python dataclasses, TypedDict, or Pydantic models. 100% client-side.
Last reviewed: April 2026New to this tool? Click here for instructions
How to Convert JSON to Python Dataclasses
To use the JSON to Python Dataclass Converter, follow these steps:
1. Paste your JSON data into the input area on the left.
2. Choose the desired output mode: Dataclass, TypedDict, or Pydantic.
3. Click the 'Convert' button to generate the Python code.
4. Copy or download the generated Python code using the buttons above the output.
- Dataclass: Generates @dataclass decorated classes with type hints.
- TypedDict: Generates TypedDict subclasses for static type analysis.
- Pydantic: Generates BaseModel subclasses with runtime validation.
When to Use the Tool in Real Workflows
This tool is ideal for developers working with JSON data in Python projects. It helps in:
1. Improving code readability and maintainability.
2. Ensuring type safety in data handling.
3. Generating boilerplate code for API responses or configuration data.
How It Works
The JSON to Python Dataclass Converter works by:
1. Parsing the JSON input.
2. Mapping JSON types to their Python equivalents using the typing module.
3. Generating Python code based on the selected output mode.
4. Providing options to customize the generated code (e.g., naming conventions).
Tips, Edge Cases, or Limitations
1. Ensure your JSON data is well-formed and valid.
2. For nested objects, consider using libraries like dacite for automatic instantiation.
3. Pydantic mode requires the Pydantic library to be installed in your project.
4. The converter uses PascalCase for class names and replaces invalid characters with underscores.
Frequently Asked Questions
Paste any JSON object and get a ready-to-use Python dataclass, TypedDict, or Pydantic v2 model — with accurate type hints and nested class support. The converter handles deeply nested objects, nullable fields, camelCase-to-snake_case renaming, and empty arrays, emitting classes in dependency order so the output drops straight into your project.
What This Tool Does
The converter accepts arbitrary JSON and produces one of three Python class formats: a standard @dataclass per PEP 557, a TypedDict per PEP 589, or a Pydantic v2 BaseModel. For every JSON value it encounters, it infers the correct Python type hint — str, int, float, bool, list, dict, or None. Nested JSON objects each get their own class definition, emitted before the parent so the output is immediately importable. A Python version toggle switches between the legacy Optional[X] form and the X | None union syntax introduced in Python 3.10. Nothing leaves your browser — no JSON payload is uploaded or stored at any point.
How to Use It
Step-by-step walkthrough
- Paste JSON into the input pane on the left (or click Try Example to load the GitHub pull request webhook payload).
- Choose output format using the toggle:
@dataclass,TypedDict, orPydantic v2 BaseModel. - Set your Python version: 3.10+ emits
X | None; 3.9 and below emitsOptional[X]with afrom typing import Optionalimport. - Click Generate. The tool traverses the JSON tree depth-first and emits child classes before parents.
- Copy the output with the Copy button and paste it directly into your project.
Generated class names are PascalCase-derived from their JSON key context — a key named pull_request produces a class named PullRequest. The root object defaults to Root unless you rename it in the settings panel; for webhook payloads, renaming it WebhookPayload is cleaner. Once you've pasted the output into a file, run mypy or pyright — a correctly generated class should produce zero type errors against the original JSON sample.
To go the other direction, the Python to JSON serializer converts dataclass and Pydantic instances back to JSON strings.
Worked example: GitHub webhook payload
The Try Example button loads a realistic GitHub pull request webhook. Below is the exact input and the Pydantic v2 output the tool produces.
Inputs
- JSON payload
-
{ "action": "opened", "pull_request": { "id": 1, "title": "Add JSON schema validation", "user": { "login": "alice-dev", "id": 12345, "avatar_url": "https://avatars.githubusercontent.com/u/12345" }, "created_at": "2024-01-15T09:47:32Z", "updated_at": "2024-01-15T14:22:11Z", "merged_at": null, "labels": [] } } - Output format
- Pydantic v2 BaseModel
- Python version
- 3.10+ (
X | Noneunion syntax)
Step-by-step
- Paste the webhook JSON into the left pane.
- Select Pydantic v2 BaseModel from the output format toggle.
- Select Python 3.10+ so the tool uses
X | Noneinstead ofOptional[X]. - Click Generate. The tool walks the tree depth-first —
user→pull_request→ root — emittingUserfirst, thenPullRequest, thenWebhookPayload. - Inspect
merged_at: str | None = None— correctly nullable because the source value is JSONnull. - Note
labels: List[Any] = []— the tool emitsList[Any]for empty arrays and adds an inline comment suggesting you regenerate with a populated payload once the array contains real items. - Copy the output and run
mypy --strictorpyrightagainst the file to confirm zero type errors.
Expected output
from __future__ import annotations
from pydantic import BaseModel, Field
from typing import Any, List
class User(BaseModel):
login: str
id: int
avatar_url: str
class PullRequest(BaseModel):
id: int
title: str
user: User
created_at: str # consider: datetime
updated_at: str # consider: datetime
merged_at: str | None = None
labels: List[Any] = []
class WebhookPayload(BaseModel):
action: str
pull_request: PullRequest
Output options explained
The Smart type inference toggle, when enabled, replaces str fields whose sampled values match RFC 3339 patterns with datetime and adds a Pydantic model_validator note for coercion. Leave it off when you want the most portable output — str is always safe, and you can refine types manually after the fact.
Dataclass vs TypedDict vs Pydantic: When to Use Each
The right choice depends on where the data comes from and what you need to do with it. These three options cover the realistic decision space for backend Python work.
@dataclass (PEP 557)
Introduced in Python 3.7, @dataclass auto-generates __init__, __repr__, and __eq__ from your field annotations. There is zero runtime validation — assigning a string to an int-annotated field at runtime silently succeeds. That's a feature, not a bug, when you own the data source: if a trusted internal service populates the object, skipping validation keeps construction fast. Benchmarks on CPython 3.12 show dataclass instantiation running roughly at the speed of a plain dict assignment, making it the right default for internal data-transfer objects constructed millions of times in a hot path.
TypedDict (PEP 589)
TypedDict is purely a static-analysis hint. It produces no __init__, no runtime class, and no enforcement of any kind — a TypedDict instance at runtime is literally a plain dict. That means zero overhead and maximum compatibility: you can annotate existing dict-returning functions without changing any runtime behaviour. Both mypy and pyright resolve TypedDict keys with full type narrowing, and because no extra library is needed, TypedDict has the widest editor support of the three options without any plugin configuration. The trade-off is that bad data will never raise an error at runtime — a wrong-typed field goes undetected until it crashes elsewhere.
Pydantic v2 BaseModel
Pydantic v2's BaseModel runs field validation on instantiation using a compiled Rust core (pydantic-core). Call WebhookPayload.model_validate(json.loads(raw_json)) — the v2 replacement for the deprecated v1 parse_obj() method — and Pydantic raises a ValidationError listing every field that fails type coercion. Per the official Pydantic v2 benchmarks, model construction runs approximately 2–5× slower than a plain dataclass because of the validation pass. That's rarely material for I/O-bound API handlers but is worth profiling in tight loops. FastAPI uses Pydantic v2 models for both request body parsing and automatic OpenAPI schema generation, so a generated BaseModel can be copy-pasted directly as a route annotation. All three formats are understood by mypy and pyright; Pydantic ships its own mypy plugin for more precise inference.
| Feature | @dataclass (PEP 557) | TypedDict (PEP 589) | Pydantic v2 BaseModel |
|---|---|---|---|
| Runtime validation | None | None | Yes — raises ValidationError |
| Serialization built-in | No (use dataclasses.asdict()) |
No — it's a plain dict | Yes — model_dump() / model_dump_json() |
| IDE / type-checker support | Yes (mypy, pyright) | Yes — widest, no plugin needed | Yes + optional mypy plugin |
| Construction speed | Fastest | N/A — no __init__ |
~2–5× slower than dataclass |
| FastAPI integration | Supported (v0.89+, no OpenAPI schema) | Partial | Native — full OpenAPI generation |
| Recommended use case | Internal DTOs, trusted data sources | Static typing hints, legacy dict code | API input/output validation |
JSON-to-Python Type Mapping Reference
The converter follows a deterministic set of rules for every JSON value type. Understanding these rules lets you predict and audit the output quickly.
Primitive type mappings
JSON string always maps to str. When a string value matches an ISO 8601 / RFC 3339 pattern (e.g. "2024-01-15T09:47:32Z"), the tool keeps str as the type but appends a # consider: datetime inline comment. JSON integers map to int; JSON floats map to float. If the same key holds both integer and float values across samples, the tool widens to float. JSON boolean maps to bool.
Null and optional field handling
A JSON null value for a key produces X | None = None on Python 3.10+ or Optional[X] = None on Python 3.9 and below. When the converter sees null without ever seeing a non-null value for that key in the sample, it falls back to str | None = None — a conservative choice that keeps the field usable. Fields absent from some JSON samples need the same treatment; regenerating with several representative payloads is the safest way to catch all nullable paths.
Arrays and heterogeneous lists
An array of consistently-typed objects becomes List[ChildClass]. An empty array ([]) becomes List[Any] with a comment, because there is no sample data from which to infer element type. Heterogeneous arrays — where elements are a mix of types — produce List[Union[X, Y]] for two distinct types, or List[Any] with a warning comment for three or more. The Pydantic schema validator can help you confirm element types after you've populated those fields with real data.
Nested objects
Each nested JSON object gets its own named class. An empty object ({}) is inferred as Dict[str, Any] — regenerate once you have a populated sample to get a proper class definition.
| JSON Type | Example Value | Generated Python Type | Notes / Caveats |
|---|---|---|---|
| string | "alice-dev" |
str |
ISO 8601 strings flagged with # consider: datetime |
| number (integer) | 12345 |
int |
Safe for arbitrarily large values in Python |
| number (float) | 3.14 |
float |
Mixed int/float column widens to float |
| boolean | true |
bool |
— |
| null | null |
str | None (3.10+) / Optional[str] (≤3.9) |
Defaults to = None; base type inferred from other samples |
| array of objects | [{"id": 1}] |
List[ChildClass] |
Child class emitted before parent in output |
| empty array | [] |
List[Any] |
Comment added: regenerate with populated array |
| object | {"login": "alice"} |
Nested dataclass / TypedDict / BaseModel |
Empty {} falls back to Dict[str, Any] |
Common JSON-to-Python Gotchas
Optional fields and missing keys
The most common production bug with generated classes is a field that appears in some API responses but not others. If your sample JSON always includes every key, the generated class won't mark absent fields as optional — and WebhookPayload.model_validate() will raise a ValidationError the first time a real response omits one. Collect several representative payloads before generating, and manually add = None defaults to any field you know can be absent. The merged_at: null in the worked example is the clean case — the source value was explicitly null, so the tool correctly emits merged_at: str | None = None rather than merged_at: str.
Deeply nested and recursive structures
Self-referential JSON — a tree node that contains a list of nodes of the same type — requires a forward reference. On Python 3.10+ with from __future__ import annotations at the top of the file, all annotations are treated as strings and forward references resolve automatically. Without that import, you must quote the class name: children: List["TreeNode"]. The generator always emits from __future__ import annotations for exactly this reason, so recursive structures work without manual edits.
Reserved Python keyword collisions
JSON APIs frequently use keys like type, id, from, or class — some of which are Python reserved words. id is safe as a field name (it shadows the built-in but is not a reserved keyword). from and class will cause a SyntaxError. For Pydantic output, the tool renames the field to from_ or class_ and adds Field(alias='from') or Field(alias='class') so serialization round-trips correctly. For @dataclass output, you get the renamed field with an inline comment and must handle aliasing manually — typically via a __post_init__ or a wrapper function.
Numeric type ambiguity
GitHub's API returns user IDs like 12345 as JSON integers. Python's int handles arbitrarily large values with no overflow risk, so there's no issue on the Python side. The gotcha appears at interop boundaries: if you later serialize this value to a language with 32-bit integers (Java, C#), values above 231−1 will silently overflow unless you document the constraint. Generate as int but add a comment for any ID field that could plausibly exceed that bound. Also watch for JSON payloads from weakly-typed sources that mix 1 and 1.0 for the same field — the converter widens to float in that case, which may surprise consumers expecting an exact integer.
Real-World Example: GitHub Pull Request Webhook to Pydantic Models
Input payload
The following JSON is an abridged GitHub pull_request webhook with a nested user object, two ISO 8601 timestamps, one explicitly nullable field (merged_at), and an empty labels array — a representative sample of the ambiguities a real API produces.
{
"action": "opened",
"pull_request": {
"id": 1,
"title": "Add JSON schema validation",
"user": {
"login": "alice-dev",
"id": 12345,
"avatar_url": "https://avatars.githubusercontent.com/u/12345"
},
"created_at": "2024-01-15T09:47:32Z",
"updated_at": "2024-01-15T14:22:11Z",
"merged_at": null,
"labels": []
}
}
Generated Pydantic v2 output
With output format set to Pydantic v2 BaseModel and Python version set to 3.10+, the tool emits the following. User appears first because PullRequest depends on it; WebhookPayload appears last. The created_at and updated_at fields stay as str with a suggestion comment — switching them to datetime requires adding a model_validator or using Pydantic's built-in AwareDatetime type.
from __future__ import annotations
from pydantic import BaseModel, Field
from typing import Any, List
class User(BaseModel):
login: str
id: int
avatar_url: str
class PullRequest(BaseModel):
id: int
title: str
user: User
created_at: str # consider: datetime
updated_at: str # consider: datetime
merged_at: str | None = None
labels: List[Any] = []
class WebhookPayload(BaseModel):
action: str
pull_request: PullRequest
To validate a raw response string against this model, call model_validate() rather than the deprecated v1 parse_obj():
import json
from pydantic import ValidationError
raw = '{"action":"opened","pull_request":{...}}'
try:
payload = WebhookPayload.model_validate(json.loads(raw))
except ValidationError as exc:
print(exc.errors()) # lists every failing field
Using the model in a FastAPI endpoint
FastAPI resolves Pydantic v2 models to OpenAPI request body schemas automatically. Paste the generated class into your routes file and annotate the handler directly:
from fastapi import FastAPI
app = FastAPI()
@app.post("/webhook")
async def handle_pr_event(payload: WebhookPayload):
pr_title = payload.pull_request.title
author = payload.pull_request.user.login
return {"received": pr_title, "from": author}
FastAPI rejects malformed requests with a 422 Unprocessable Entity response before your handler code runs, and the OpenAPI docs at /docs show the full schema derived from your generated classes. For @dataclass output, FastAPI also accepts Python dataclasses as of version 0.89+, but OpenAPI schema generation is less complete than with Pydantic models.