JSON Toolkit

Какую структуру должен использовать ответ API

Главное: Голые объекты чисты, пока не нужны метаданные; полные оболочки безопасны, но многословны. Для большинства проектов используйте HTTP-статус для результата и стабильную оболочку для данных.

Средний~4 мин чтенияОпубликовано 2026-10-30Обновлено 2026-10-30
Три формы ответа API и их компромиссы

Главное: выражайте результат через коды статуса 4xx/5xx, несите данные в { "data": ..., "meta": ... }, а при ошибке переключайтесь на оболочку error; этого достаточно для внутренних API, и только публичные платформы со множеством интеграторов нуждаются в JSON:API или OpenAPI.

Сначала исключите худший вариант: всегда возвращать 200 с результатом в поле code.

Три формы

A. Голый объект

{ "id": 1, "name": "demo", "price": 99 }

Чисто, и семантику выполняет HTTP (404 — это 404). Но в момент, когда вам нужны пагинация, предупреждения или trace id, их некуда поместить без изменения контракта.

B. Стабильная оболочка

{
  "data": { "id": 1, "name": "demo" },
  "meta": { "requestId": "abc", "took": 12 }
}

Данные и метаданные живут отдельно, поэтому новые метаданные никогда не перестраивают data. Цена — один уровень вложенности.

C. Всё в оболочке (избегайте)

{ "code": 0, "message": "success", "data": {...} }

Порок не в форме, а в практике. Статус 200 для любого результата означает, что CDN, шлюзы, автоматические выключатели, политики повторов и дашборды видят успех. Затем каждое оповещение требует собственного парсинга.

Рекомендация

HTTP-статус для результата, оболочка для данных.

{
  "data": { "id": 1, "name": "demo" },
  "meta": { "requestId": "req_8f2a", "took": 12 }
}
{
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "Order does not exist or was deleted",
    "details": [{ "field": "id", "issue": "not_found" }]
  },
  "meta": { "requestId": "req_8f2a" }
}

Статус 4xx или 5xx; code стабильный и машиночитаемый; message для людей; details для ошибок полей формы.

Жёсткие правила

  1. Конечные точки списков должны нести метаданные пагинации. Без total или курсора нет постраничного просмотра.
  2. Пустые списки возвращают [], никогда null. null.map — повторяющийся сбой.
  3. Метки времени в ISO 8601 со смещением. См. в каком формате метки времени.
  4. Одно соглашение об именовании. snake_case или camelCase, конвертированное один раз на границе.
  5. Деньги как целые минорные единицы или строки. 99.99 неточно в двоичной плавающей точке.
  6. Не смешивайте null с отсутствием. См. null или опустить поле.

Когда принимать спецификацию

  • Публичные платформы со множеством интеграторов: JSON:API или OpenAPI плюс общая оболочка
  • Внутренние API одной команды: оболочки выше достаточно
  • Строгие контракты: Schema сначала, структуры генерируются из неё

Спецификации экономят обсуждения и стоят онбординга. Для команды из пяти человек JSON:API обычно — отрицательная работа.

Вопросы и ответы

Как выбрать между голым объектом и оболочкой?

Используйте оболочку, когда нужны пагинация, предупреждения или requestId, чтобы meta мог расти, не перестраивая data. Возвращайте голый объект только когда метаданные не нужны и вы можете принять, что добавление полей позже изменит контракт.

Почему API никогда не должен возвращать 200 для любого результата?

CDN, шлюзы, автоматические выключатели, политики повторов и дашборды читают HTTP-статус, чтобы решить, кешировать, повторять или оповещать. С 200 везде они считают сбои успехами, и каждое оповещение требует собственного парсинга бизнес-кода.

Должны ли конечные точки списков нести метаданные пагинации?

Да. Без total или курсора нет постраничного просмотра, а пустые списки должны возвращать [], а не null, иначе вызов .map во фронтенде ломается.

Дополнительное чтение