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
Scan with WeChat to share this tool
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,typingin Python 3.11+,typing_extensionsbefore that).Optional[...]only means the value may beNone; 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
nullbecomesOptional[Any]; replaceAnywith the real type.- Keys that cannot be field names as they are (keywords such as
class, keys likeuser-idor2fa, keys that NFKC would change such as full-widthname, 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_ormodel_or name aBaseModelattribute such asjson) get a valid name. @dataclass notes the JSON key in a comment (map it yourself beforeRoot(**data)), Pydantic addsField(alias=...)somodel_validate()reads the original key (model_dump()writes the field names; usemodel_dump(by_alias=True)to get the JSON keys back), and TypedDict switches to the functional formTypedDict("Name", {...}), which keeps every key. See the example below. - Dates stay
str; Pydantic converts ISO 8601 strings if you change the type todatetime. - 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.0reaches the generator as the integer10(JavaScript'sJSON.parsekeeps no trailing zeros), soprice: int. Pydantic then rejects a later10.5withInput should be a valid integer, got a number with a fractional part; change the field tofloatwhen 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 keywordclass→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), andRootwhen 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.