Skip to content
Назад к блогу
Руководства

JSON в Python: dataclass, Pydantic и TypedDict (гайд 2026)

Правильно преобразуйте JSON в классы Python: dataclass, Pydantic v2 или TypedDict, типизация Optional, алиасы camelCase и подводные камни. Бесплатно, онлайн.

12 мин чтения

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 занимает три шага:

  1. Вставьте свой JSON. Поместите объект, массив или сырой ответ API в поле ввода. Конвертация запускается мгновенно и полностью на стороне клиента.
  2. Выберите dataclass, Pydantic или TypedDict. Переключателем Output смените стиль вывода и переименуйте Root по умолчанию во что-то осмысленное — например, User или ApiResponse.
  3. Скопируйте или скачайте. Получите сгенерированный Python одним кликом и вставьте models.py прямо в свой проект.

На этом весь транзакционный путь завершён. Если ваши данные минифицированы или вы не уверены в их корректности, сначала прогоните их через форматировщик JSON, чтобы конвертер получил чистый, правильно сформированный JSON. Остальная часть гайда объясняет результат, чтобы вы могли исправить случаи, которые инструмент не может вывести самостоятельно.

Как типы JSON отображаются на Python

У каждого значения JSON есть аналог в Python, и соответствие получается прямолинейным:

Значение JSONТип Python
"text"str
42int (произвольная точность, без переполнения)
3.14, 2e3float
true / falsebool
nullOptional[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 — когда нужны только статические подсказки типов поверх уже имеющихся словарей.

КритерийdataclassPydantic v2TypedDict
ЗависимостьСтандартная библиотека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_, fromfrom_. В режиме 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
Разобрать dictmodel_validate(data)parse_obj(data)
Разобрать сырой JSONmodel_validate_json(text)parse_raw(text)
Настроить модельmodel_config = ConfigDict(...)вложенный class Config
Сериализовать в dictmodel_dump()dict()
Сериализовать в JSONmodel_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.

Теги: python json pydantic type-safety developer-tools

Похожие статьи

Все статьи