JSON to Python Dataclass

Generate Python dataclasses from JSON instantly. Supports @dataclass, Pydantic v2 BaseModel, and TypedDict. Handles nested objects, arrays, optional fields, and custom root class names. Free, runs in your browser.

  • Runs in your browser
  • Tool input is not uploaded to ZeroTool servers
  • Free · No Sign-Up
A blank name uses Root. Changing the name generates again after 300 ms.
Mode Choose @dataclass, Pydantic v2 or TypedDict. Changing the mode generates again immediately.
Replace JSON with a sample containing nested objects, arrays and null, then generate immediately with the current settings.
Clear JSON, output and status. Keep the root name and the selected mode. Ctrl/⌘+L with focus in the tool also clears the root name.
Enter valid JSON without comments or trailing commas. Generation runs 300 ms after typing stops; invalid JSON clears the output and shows an error.
Python Output
Copy the complete displayed Python output. Empty output is not copied.
Save the complete displayed output as a .py file. The filename uses the lowercase root name; characters outside ASCII letters, digits and underscores become underscores. Empty output is not downloaded.

Enter JSON or load the example to generate Python.

Examples, details and FAQ Worked examples, how it compares with other tools, and answers to common questions.

Type Mapping

  • null → Optional[Any] = None
  • string → str
  • integer → int
  • float → float
  • boolean → bool
  • array → List[T]
  • mixed array → List[Union[T1, T2]]
  • nested object → separate class

Example

Given this JSON:

{"user": {"first_name": "Alice", "age": 30}, "tags": ["admin"], "note": null}

The tool generates (@dataclass mode):

from dataclasses import dataclass
from typing import Any, List, Optional

@dataclass
class User:
    first_name: str
    age: int

@dataclass
class Root:
    user: User
    tags: List[str]
    note: Optional[Any] = None

Example: Nested Objects and Missing Keys

Input:

{"id": 7, "title": "Release notes", "author": {"name": "Alice", "email": null}, "tags": ["python", "json"], "score": 4.5, "comments": [{"user": "bob", "text": "nice"}, {"user": "carol"}]}

Output in @dataclass mode:

from dataclasses import dataclass
from typing import Any, List, Optional

@dataclass
class Author:
  name: str
  email: Optional[Any] = None

@dataclass
class CommentsItem:
  user: str
  text: Optional[str] = None

@dataclass
class Root:
  id: int
  title: str
  author: Author
  tags: List[str]
  score: float
  comments: List[CommentsItem]

Classes are ordered so that each one is defined before it is used. text is missing from the second comment, so it becomes Optional[str] = None.

dataclass, Pydantic or TypedDict

The three modes describe the same shape but behave differently at runtime (checked with Python 3.12 and Pydantic 2.13):

root = Root(**json.loads(raw))
type(root.author)          # <class 'dict'>, not Author
root = Root.model_validate(json.loads(raw))   # Pydantic mode
root.comments[1]           # CommentsItem(user='carol', text=None)
  • @dataclass does not validate or convert. Root(**data) keeps nested objects as plain dicts and accepts wrong types. Use it when the data is already trusted, or convert nested fields yourself.
  • Pydantic validates types and builds nested models; use it for API responses you do not control. model_validate_json(raw) parses and validates in one step.
  • TypedDict only describes dict types for a type checker; nothing is checked at runtime. Every key is required by default, so the tool marks a key that is missing from some items as NotRequired[...] (PEP 655, typing in Python 3.11+, typing_extensions before that). Optional[...] only means the value may be None; the key would still be required.

Example: TypedDict with Missing Keys

Two products where note is null in the first and absent from the second, and discount only appears in the second (root class name Product, TypedDict mode):

[{"id": 1, "sku": "PEN-01", "price": 2, "note": null}, {"id": 2, "sku": "INK-07", "price": 1.5, "discount": 0.1}]
from typing import Any, NotRequired, Optional, TypedDict

class Product(TypedDict):
  id: int
  sku: str
  price: float
  note: NotRequired[Optional[Any]]
  discount: NotRequired[float]

price is 2 in one item and 1.5 in the other; PEP 484 lets an int stand in for a float, so the field is float. At runtime Product.required_keys is {'id', 'sku', 'price'} and optional_keys is {'note', 'discount'} (checked with Python 3.12). A type checker then accepts a product without discount and rejects one without sku (mypy 2.3.1: Missing key "sku" for TypedDict "Product").

Limits

  • null becomes Optional[Any]; replace Any with the real type.
  • Keys that cannot be field names as they are (keywords such as class, keys like user-id or 2fa, keys that NFKC would change such as full-width name, keys that start with __, keys named like something the output imports or uses as a type (str, Field) or like a generated class, and in Pydantic keys that start with _ or model_ or name a BaseModel attribute such as json) get a valid name. @dataclass notes the JSON key in a comment (map it yourself before Root(**data)), Pydantic adds Field(alias=...) so model_validate() reads the original key (model_dump() writes the field names; use model_dump(by_alias=True) to get the JSON keys back), and TypedDict switches to the functional form TypedDict("Name", {...}), which keeps every key. See the example below.
  • Dates stay str; Pydantic converts ISO 8601 strings if you change the type to datetime.
  • Arrays of primitives at the root generate no class. In a root array that mixes objects and other values, the objects become the root class and the whole array gets an alias at the end, ProductArray = List[Union[...]] (see below).
  • A number written as 10.0 reaches the generator as the integer 10 (JavaScript's JSON.parse keeps no trailing zeros), so price: int. Pydantic then rejects a later 10.5 with Input should be a valid integer, got a number with a fractional part; change the field to float when prices can have decimals.
  • The root class name is used as typed when it is a Python identifier; otherwise it is converted: order-item → OrderItem, 2fa → _2fa, the keyword class → class_, a leading __ becomes one _ (nested class names start with the root name, and Python mangles names that start with two underscores inside a class body), and Root when no identifier character is left.

Keys That Are Not Field Names

A MongoDB-style _id, a keyword, a dashed key and json, which is a BaseModel method (root class name Ticket, Pydantic v2 mode):

{"_id": "65f0c1", "class": "economy", "user-id": 7, "json": null}
from pydantic import BaseModel, Field
from typing import Any, Optional

class Ticket(BaseModel):
  id: str = Field(alias="_id")
  class_: str = Field(alias="class")
  user_id: int = Field(alias="user-id")
  json_: Optional[Any] = Field(default=None, alias="json")

In @dataclass mode _id and json stay (they are valid there) and the other two get class_ and user_id with a # JSON key comment; in TypedDict mode the output is Ticket = TypedDict("Ticket", {"_id": str, "class": str, ...}). The tool's test runs all three in Python 3.12 and 3.14 and reads samples back.

Repeated Key Names

Two meta objects with different fields get two classes; the second takes the parent name as a prefix:

{"a": {"meta": {"x": 1}}, "b": {"meta": {"y": 2}}}
from dataclasses import dataclass

@dataclass
class Meta:
  x: int

@dataclass
class BMeta:
  y: int

@dataclass
class A:
  meta: Meta

@dataclass
class B:
  meta: BMeta

@dataclass
class Root:
  a: A
  b: B

A meta with the same fields as a.meta would reuse Meta; if BMeta were taken as well, the next shape would be Meta2. Every sample of a key counts: the objects of one key in a root array, or in arrays inside it, merge into one class, and a key missing from some of them becomes optional. A key outside ASCII keeps all its characters in the class name, and because a class never takes the name of a JSON key (a Pydantic field with a default would hide a class of the same name), {"收货地址": {...}} under root Order gives the class Order收货地址.

Root Arrays With Other Values

A root array can hold values that are not objects. They are kept in an alias after the classes (root class name Product, Pydantic v2 mode):

[{"sku": "PEN-01", "price": 2}, "sold out", null, {"sku": "INK-07", "price": 1.5}]
from pydantic import BaseModel
from typing import List, Union

class Product(BaseModel):
  sku: str
  price: float

ProductArray = List[Union[str, None, Product]]

TypeAdapter(ProductArray).validate_python(data) validates the whole array in Pydantic; the tool's test runs it with Pydantic 2.13.5. A null element becomes None in the union.

Empty arrays and sample coverage

An empty array gives the generator no evidence about its element type, so {"tags": []} produces tags: List[Any]. Include another object with {"tags": ["admin"]} in the same input array and the merged field becomes List[str]. The generator uses the samples you provide. It cannot infer a field that appears in none of them. Include representative objects with missing keys and null values before copying the classes.

FAQ

What does this tool generate?

It generates Python class definitions from JSON. Choose between standard @dataclass (stdlib), Pydantic v2 BaseModel, or TypedDict. Nested objects become their own named classes.

What output modes are supported?

Three modes: @dataclass (Python stdlib, default), Pydantic v2 BaseModel, and TypedDict (Python 3.8+; NotRequired needs 3.11+ or typing_extensions).

How are JSON types mapped to Python types?

string → str, integer → int, other numbers → float, boolean → bool, null → Optional[Any], array → List[T] (List[Union[...]] for mixed elements, List[Any] when empty), nested object → separate class. A number written as 10.0 is read as the integer 10, so it becomes int.

When are fields marked as Optional?

In @dataclass and Pydantic mode a field becomes Optional[T] = None, and moves after the required fields, when its value is null in a sample or when it is missing from some objects of a merged array. TypedDict keeps the JSON key order: a key missing from some objects becomes NotRequired[T] and a null value becomes Optional[T].

How are nested objects handled?

Each nested object becomes a class named after its key in PascalCase; objects inside an array get the suffix Item (comments → CommentsItem). Classes are written before the classes that use them. Objects under the same key with the same fields share one class; another shape gets the parent name as a prefix (b.meta → BMeta), then a number. Class names keep every letter of a key outside ASCII (PEP 3131) and, except the root class name, never equal a JSON key, a keyword or a name the output imports.

Is my data sent to a server?

No. The JSON and the root class name are processed in this browser tab and are not sent anywhere, and the tool does not save them in browser storage. The page records an analytics event with only the tool name and the action (generate) when you finish an edit (the field loses focus), click Example or change the mode, but not again when the JSON, root class name and mode are the same as for the previous event.