JSON в Python: dataclass, Pydantic и TypedDict (гайд 2026)
Вставьте свой JSON в конвертер JSON в Python и скопируйте сгенерированные классы. Это быстрый путь превращения JSON в Python: ничего не нужно устанавливать, ничего не загружается на сервер, всё выполняется в браузере. Для разовой модели или беглого взгляда на незнакомый ответ API это дело нескольких секунд.
Задача посложнее — та, которую конвертер решить за вас не может. Он выводит правильные типы, но не может принять главное решение: какой из трёх вариантов вывода вам нужен. dataclass, BaseModel из Pydantic v2 и TypedDict могут описывать один и тот же JSON, но различаются силой типобезопасности, поведением во время выполнения и тем, как в них загружаются данные. Ошибётесь с выбором — и либо будете зря тащить зависимость Pydantic, либо решите, что dataclass проверяет входные данные, хотя он молча этого не делает.
В этом гайде разбирается матрица выбора для всех трёх вариантов, как конвертер выводит типы, обработка camelCase в каждом режиме, как разобрать JSON с помощью сгенерированного класса (включая ловушку с вложенностью, на которой все спотыкаются), Pydantic v2 против v1, а также пограничные случаи, которые стоит знать, прежде чем доверять результату.
Как конвертировать JSON в Python
Конвертация JSON в Python занимает три шага:
- Вставьте свой JSON. Поместите объект, массив или сырой ответ API в поле ввода. Конвертация запускается мгновенно и полностью на стороне клиента.
- Выберите dataclass, Pydantic или TypedDict. Переключателем Output смените стиль вывода и переименуйте
Rootпо умолчанию во что-то осмысленное — например,UserилиApiResponse. - Скопируйте или скачайте. Получите сгенерированный Python одним кликом и вставьте
models.pyпрямо в свой проект.
На этом весь транзакционный путь завершён. Если ваши данные минифицированы или вы не уверены в их корректности, сначала прогоните их через форматировщик JSON, чтобы конвертер получил чистый, правильно сформированный JSON. Остальная часть гайда объясняет результат, чтобы вы могли исправить случаи, которые инструмент не может вывести самостоятельно.
Как типы JSON отображаются на Python
У каждого значения JSON есть аналог в Python, и соответствие получается прямолинейным:
| Значение JSON | Тип Python |
|---|---|
"text" | str |
42 | int (произвольная точность, без переполнения) |
3.14, 2e3 | float |
true / false | bool |
null | Optional[T] / Optional[Any] |
[1, 2, 3] | List[T] |
{ ... } | именованный класс |
Возьмём типичный payload из REST-API:
{ "id": 101, "name": "Ada Lovelace", "email": "ada@example.com", "active": true, "roles": ["admin", "user"] }
В режиме dataclass конвертер выдаёт готовый к использованию класс:
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
email: str
active: bool
roles: List[str]
В одном Python отличается от остальных языков. Его int имеет произвольную точность, поэтому снежинка Discord или любой ID больше 2^53 остаётся обычным int без потерь. JavaScript на том же значении потерял бы точность, а Rust заставил бы выбирать между i64, u64 и f64. Как float выводится только значение, записанное с десятичной точкой или экспонентой. Поле, которое всегда только null, по одному образцу типизировать нельзя, поэтому оно откатывается к Optional[Any]; вставьте представительный payload с заполненным значением, чтобы получить что-то точнее, чем Any.
dataclass, Pydantic v2 или TypedDict — что выбрать?
Это как раз тот выбор, который конвертер оставляет вам, и ради него написан весь гайд. Если коротко: берите dataclass, когда нужен внутренний контейнер данных без зависимостей, Pydantic v2 — когда вы разбираете или проверяете недоверенный вход, и TypedDict — когда нужны только статические подсказки типов поверх уже имеющихся словарей.
| Критерий | dataclass | Pydantic v2 | TypedDict |
|---|---|---|---|
| Зависимость | Стандартная библиотека | pip install pydantic | Стандартная библиотека (typing) |
| Проверка во время выполнения | Нет | Да — проверяет и приводит типы | Нет — только подсказки |
| Затраты во время выполнения | Небольшие (настоящий объект) | Есть (проверка) | Нулевые (это dict) |
| Автоматический разбор вложенности | Нет, вручную | Да, model_validate рекурсивно | Нет, это dict |
| Алиасы camelCase | Сохраняет исходный ключ | Field(alias=...) | Функциональный синтаксис |
| Лучше всего для | Внутренних структур | Ответов API, webhook, конфигов | Типизации старого кода на словарях |
Для небольшой формы вроде {"id": 101, "name": "Ada Lovelace", "active": true} все три цели выглядят почти одинаково. dataclass для JSON в Python даёт вам объект из стандартной библиотеки:
from dataclasses import dataclass
@dataclass
class Root:
id: int
name: str
active: bool
Вариант с Pydantic подставляет BaseModel — выглядит похоже, но оправдывает себя во время выполнения:
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
active: bool
А TypedDict из JSON описывает форму обычного словаря и ничего нового не добавляет:
from typing import TypedDict
class Root(TypedDict):
id: int
name: str
active: bool
Одни и те же поля — три совершенно разных контракта. Ценность генерации всех трёх из одного образца в том, что вы можете переключаться между ними в инструменте и подбирать режим под задачу: Pydantic — когда вы разбираете данные, которые не контролируете, dataclass — когда данные внутренние, а TypedDict — когда вы просто хотите, чтобы mypy понимал словарь, который вы и так передаёте по коду.
Как конвертер выводит классы
Три правила покрывают почти всё, что вы в него подадите: один класс на форму объекта, слияние массивов ключ за ключом и аккуратная обработка отсутствующих значений.
Структурный вывод: один именованный класс на объект
Каждая отдельная форма объекта становится собственным именованным классом. Вложенные объекты не встраиваются — они выносятся в отдельные определения, на которые идут ссылки:
{ "repo": "pydantic", "owner": { "login": "samuelcolvin", "id": 100 } }
from dataclasses import dataclass
@dataclass
class Owner:
login: str
id: int
@dataclass
class Root:
repo: str
owner: Owner
Вложенный owner становится собственным классом Owner, на который ссылается поле. Обратите внимание на порядок: дочерний класс выводится раньше родительского, который его использует, поэтому модуль работает без from __future__ import annotations. Одинаковые формы тоже дедуплицируются, так что два поля с одинаковой структурой используют один класс, а не порождают копии.
Слияние массивов и поля Optional
Когда вы передаёте массив объектов, конвертер сливает их ключ за ключом в единый тип элемента. Ключ, присутствующий в одних элементах, но отсутствующий в других, становится Optional:
{ "users": [{ "id": 1, "nick": "x" }, { "id": 2 }] }
from typing import List, Optional, TypedDict
class User(TypedDict):
id: int
nick: Optional[str]
class Root(TypedDict):
users: List[User]
id присутствует в каждом элементе, поэтому остаётся обязательным. nick отсутствует у второго пользователя, поэтому становится Optional[str]. Именно поэтому один объект — слабый образец: инструмент может пометить поле как Optional только тогда, когда действительно где-то видит отсутствие ключа. Подайте представительный массив — и вывод отразит реальную форму.
Optional, None и пустые массивы
Некоторые значения сами по себе не несут типа, и конвертер помечает это явно. Ключ, отсутствующий в части элементов массива, становится Optional[T]. Поле, которое встречается только как null, становится Optional[Any], потому что сам по себе JSON null ничего не говорит о задуманном типе. Пустой массив или массив со смешанными типами элементов откатывается к List[Any]. Ничего из этого не баг: инструмент просто не гадает. Замените каждый Any на конкретный тип, как только появится образец, который его показывает, и помните: благодаря int произвольной точности в Python большой ID никогда не вынудит вас переходить к более широкому числовому типу, как это было бы в Rust или JavaScript.
Обработка ключей camelCase в каждом режиме
Ключи JSON часто в camelCase, тогда как идиоматичные поля Python — в snake_case. Единственно верного ответа тут нет, поэтому каждый режим решает это по-своему, и разницу стоит понять, прежде чем копировать результат.
dataclass. У dataclass нет встроенного механизма алиасов. Чтобы Root(**data) продолжал работать, конвертер сохраняет исходный ключ как имя поля всякий раз, когда это допустимый идентификатор Python, поэтому publicRepos остаётся publicRepos. Принудительный перевод в snake_case сломал бы **data с ошибкой TypeError.
Pydantic v2. Здесь вы получаете идиоматичный результат. Поля переименовываются в snake_case с Field(alias=...), который сопоставляет их обратно с точным ключом JSON, плюс model_config = ConfigDict(populate_by_name=True), чтобы можно было строить модель как по имени поля, так и по алиасу:
from pydantic import BaseModel, ConfigDict, Field
class Root(BaseModel):
model_config = ConfigDict(populate_by_name=True)
login: str
public_repos: int = Field(alias="publicRepos")
created_at: str = Field(alias="createdAt")
Благодаря этому Root.model_validate(api_json) без потерь обрабатывает реальные payload в camelCase, пока ваш код читается естественно.
TypedDict. Обычные ключи-идентификаторы используются напрямую. Как только появляется ключ, не являющийся идентификатором, конвертер переключается на функциональный синтаксис TypedDict('User', {...}), чтобы сохранить точный ключ, — об этом следующий раздел.
Ключевые слова Python и ключи, не являющиеся идентификаторами
Ключи JSON — это просто строки, поэтому ничто не мешает API прислать class, from или first-name. В качестве голого атрибута Python каждый из них вызвал бы синтаксическую ошибку, поэтому конвертер их обезвреживает.
Ключ, совпадающий с ключевым словом Python, получает завершающее подчёркивание: class становится class_, from — from_. В режиме Pydantic он вдобавок сохраняет Field(alias=...), чтобы модель по-прежнему читала исходный ключ из данных:
from pydantic import BaseModel, ConfigDict, Field
class Root(BaseModel):
model_config = ConfigDict(populate_by_name=True)
class_: str = Field(alias="class")
from_: str = Field(alias="from")
Ключи с дефисами, пробелами или начальной цифрой обезвреживаются до допустимых идентификаторов в режимах dataclass и Pydantic. TypedDict — единственный режим, способный сохранить буквальный ключ: он использует функциональную форму, поэтому "first-name" остаётся нетронутым:
from typing import TypedDict
Root = TypedDict("Root", {"first-name": str, "2fa": bool})
Результат всегда работает, а в режиме TypedDict ключ остаётся байт в байт таким, каким его прислал API.
Разбор JSON с помощью сгенерированного класса
Сгенерировать класс — половина дела. При загрузке JSON три режима резко расходятся, и здесь одно расхожее предположение оказывается ошибочным.
dataclass. Для плоского объекта json.loads вместе с Root(**data) работает напрямую:
import json
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
roles: List[str]
data = json.loads('{"id": 101, "name": "Ada Lovelace", "roles": ["admin"]}')
root = Root(**data)
print(root.name, root.roles)
Но dataclass не рекурсивен. Если в JSON есть вложенные объекты, Root(**data) заполнит верхний уровень, а каждого потомка оставит обычным dict, а не сгенерированным вами классом Owner. Чтобы разобрать всё дерево, соберите потомков сами, возьмите библиотеку вроде dacite или pydantic.dataclasses, либо переключите инструмент в режим Pydantic. Это та ловушка, о которой большинство конвертеров никогда не упоминает.
Pydantic v2. Один вызов разбирает и проверяет всё дерево:
import json
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
root = Root.model_validate(json.loads('{"id": 101, "name": "Ada Lovelace"}'))
print(root.name)
Несоответствие типов вызывает понятную ошибку ValidationError, а не проходит молча. В этом окупается зависимость: модель — это валидатор времени выполнения, поэтому вызов model_validate, который завершился успешно, — гарантия того, что данные подошли, а не просто убеждённость средства проверки типов в этом.
TypedDict. Во время выполнения TypedDict — это обычный dict. Аннотация data: Root = json.loads(text) нужна исключительно для средства статической проверки; при запуске кода ничего не проверяется. Если нужно настоящее принуждение к контракту, запускайте mypy в CI или переходите на Pydantic. Выбор режима — это выбор того, сколько гарантий во время выполнения вам нужно, а когда контракт должен выйти за пределы типов, гайд по валидации JSON Schema разбирает, как обеспечивать структуру от начала до конца.
Pydantic v2 против v1 — что изменилось
Конвертер выдаёт синтаксис v2. Множество кодовых баз всё ещё работает на v1, и если вставить вывод v2 в проект на v1, сбой произойдёт из-за переименованных методов, а не из-за логики. Вот соответствие:
| Задача | Pydantic v2 (вывод инструмента) | Pydantic v1 |
|---|---|---|
Разобрать dict | model_validate(data) | parse_obj(data) |
| Разобрать сырой JSON | model_validate_json(text) | parse_raw(text) |
| Настроить модель | model_config = ConfigDict(...) | вложенный class Config |
Сериализовать в dict | model_dump() | dict() |
| Сериализовать в JSON | model_dump_json() | json() |
Поведение алиасов тоже стало строже: в v2 рядом с Field(alias=...) нужен populate_by_name=True, чтобы конструировать модель как по имени поля, так и по алиасу — поэтому инструмент его и добавляет. Практическое правило короткое. По возможности переходите на v2 — он быстрее и на нём сосредоточена текущая разработка; иначе перепишите model_validate обратно в parse_obj, а ConfigDict — обратно во вложенный Config.
json-to-python против quicktype, datamodel-code-generator и ручного написания
Единственно лучшего способа сгенерировать класс Python из JSON не существует. Всё зависит от того, где живёт JSON и с чего вы начинаете.
| Подход | Лучше всего для | Примечание |
|---|---|---|
| Онлайн-конвертер (этот инструмент, jsonlint, codeshack) | Разовых конвертаций, чувствительных payload, без установки | Выводит из образца, полностью на стороне клиента |
| quicktype | Вывода на несколько языков, кодогенерации в пайплайне | Тоже на основе образца; поддержка Python слабее по алиасам и выбору режима |
| datamodel-code-generator | Генерации Pydantic из JSON Schema или OpenAPI | На входе схема, а не образец — авторитетнее, когда она у вас есть |
| Написание классов вручную | Крошечных payload, изучения синтаксиса | Полный контроль, но муторно и легко разойтись |
Разница в источнике: онлайн-конвертер и quicktype выводят типы из образца JSON, тогда как datamodel-code-generator читает как источник истины JSON Schema или спецификацию OpenAPI. Используйте инструменты на основе образца для исследования и разовых задач; берите инструмент на основе схемы, когда контракт уже существует. Если ваша кодовая база на TypeScript, а не на Python, тот же подход на основе образца работает с конвертером JSON в TypeScript, а гайд по интерфейсам JSON → TypeScript подробно разбирает эту сторону. Что касается проверки во время выполнения в строго типизированном компилируемом языке, гайд по структурам JSON → Rust даёт полезный контраст: serde — это Pydantic для Rust.
Частые ошибки при генерации Python из JSON
Сгенерированные классы — это отправная точка. Проверьте следующее, прежде чем полагаться на результат с реальными данными.
- Один образец редко раскрывает все формы.
Optionalвыводится только из элементов массива, которые действительно различаются. Вставьте представительный массив, чтобы опциональность была точной, а не догадкой по одному удачному объекту. - dataclass не проверяет.
Root(**data)только присваивает поля; он не проверяет типы и никуда не рекурсирует. Нужна проверка — используйте Pydantic. - Вложенный JSON ломает
Root(**data). Заполняется только верхний уровень, потомки остаются словарями. Перейдите наmodel_validateиз Pydantic или используйте dacite. - Поле, всегда равное
null, становитсяOptional[Any]. Считайте это заглушкой, а не обычным опциональным полем, и присвойте настоящий тип, как только узнаете его. - Вывод v2 в проекте на v1.
model_validateокажется неопределённым. Обновитесь до v2 или перепишите его какparse_obj. - Дата, оставленная как
str, не годится для арифметики дат. Смените поле наdatetimeили в Pydantic используйте полеdatetime, чтобы оно само разбирало ISO 8601.
Часто задаваемые вопросы
Что выбрать для JSON — dataclass, Pydantic или TypedDict?
Используйте dataclass как внутренний контейнер данных без зависимостей. Используйте Pydantic v2, когда вы разбираете или проверяете недоверенный вход, потому что он принуждает типы во время выполнения. Используйте TypedDict, чтобы добавить статические подсказки к уже имеющемуся словарю без затрат во время выполнения. Когда данные приходят из-за пределов вашего контроля, по умолчанию берите Pydantic.
Проверяет ли Pydantic JSON во время выполнения?
Да. Root.model_validate(data) проверяет и приводит типы по ходу выполнения кода, вызывая ValidationError на некорректном входе. dataclass не делает ни того, ни другого — Root(**data) только присваивает поля. TypedDict тоже не делает ни того, ни другого; это подсказка для средств статической проверки вроде mypy, а во время выполнения он ведёт себя как обычный словарь.
Как типизировать поле, которое JSON иногда опускает?
Типизируйте его как Optional[T]. Когда ключ встречается лишь в части элементов массива, конвертер помечает его Optional автоматически. В Pydantic задайте полю значение по умолчанию, например = None, чтобы отсутствующий ключ не вызывал ошибку валидации. Поле dataclass можно снабдить умолчанием так же — через field(default=None).
Почему мой dataclass не смог разобрать вложенный JSON?
Потому что Root(**data) не рекурсирует. Он заполняет поля верхнего уровня, а вложенные объекты оставляет обычными словарями вместо сгенерированных вами дочерних классов. Чтобы разобрать всё дерево одним вызовом, перейдите на model_validate из Pydantic либо добавьте рекурсию с помощью dacite или pydantic.dataclasses.
Каким типом Python становится большое целое число из JSON?
int. Целые числа Python имеют произвольную точность, поэтому снежинка или Discord ID больше 2^53 остаётся обычным int без потерь. Вы никогда не столкнётесь с потерей точности, как в JavaScript, и вам никогда не придётся выбирать между i64 и u64, как вынуждает Rust.
Как ключи JSON в camelCase обрабатываются в каждом режиме?
dataclass сохраняет исходный ключ, чтобы Root(**data) продолжал работать. Pydantic переименовывает поля в snake_case с Field(alias="camelKey") и populate_by_name=True, поэтому без потерь обрабатывает настоящие данные API. TypedDict использует функциональный синтаксис TypedDict('Name', {...}), чтобы сохранить любой ключ, не являющийся допустимым идентификатором.
Как обработать ключ JSON, совпадающий с ключевым словом Python, например class?
Конвертер добавляет завершающее подчёркивание, превращая class в class_, чтобы код был корректным. В режиме Pydantic он вдобавок добавляет Field(alias="class"), сопоставляя поле обратно с исходным ключом из данных, так что имя допустимо, а модель по-прежнему читает реальный payload.
Приватны ли мои данные JSON при использовании онлайн-конвертера JSON в Python?
Да. Конвертация выполняется на 100% в вашем браузере с помощью JavaScript. Ваш JSON — включая token, идентификаторы и данные клиентов — не покидает страницу и никогда не отправляется на сервер. После загрузки страницы всё работает даже офлайн. Когда будете готовы, попробуйте конвертер JSON в Python на своём собственном payload.