Опирается на правила:
R-FLD-1..7,R-RSP-1..8и X-коды из REST API Style Guide → раздел JSON и формат ответов.
Важно знать
- camelCase для полей JSON (
orderId,createdAt,totalAmount).- Даты и время — ISO 8601 (
2026-05-26T10:30:00Zили2026-05-26).- Enum-значения —
UPPER_SNAKE_CASE(IN_PROGRESS,CREDIT_CARD).- Коллекции — множественное число (
items,errors,tags).- Идентификаторы — суффикс
Id(orderId,customerId).nullв PATCH body — команда удалить поле (JSON Merge Patch).nullв response 2xx запрещён — отсутствующее поле = нет в JSON.- Envelope (
{ success: true, data: ... }) — запрещён.- Коллекция —
{ "content": [...] }(не envelope, а структура пагинации).
JSON-контракт — лицо API. Каждое значение по умолчанию — что-то значит: null в response может означать «нет данных», «удалено», «не загружено», «error» — UCP убирает эту неопределённость. Нет null — нет поля.
Имена полей
R-FLD-1..5:
{
"orderId": "550e8400-...", ✓ camelCase + Id-suffix
"createdAt": "2026-05-26T10:30:00Z", ✓ ISO 8601
"totalAmount": 1500.00, ✓ camelCase
"status": "IN_PROGRESS", ✓ UPPER_SNAKE_CASE enum
"paymentMethod": "CREDIT_CARD", ✓
"deliveryAddress": { ✓ camelCase
"streetName": "Ленина",
"zipCode": "123456"
},
"items": [...], ✓ множественное число
"tags": ["electronics", "sale"] ✓
}
Id-суффикс для идентификаторов:orderId,customerId,parentCategoryId.- ISO 8601 для дат:
2026-05-26(date),2026-05-26T10:30:00Z(date-time UTC). UPPER_SNAKE_CASEдля enum:IN_PROGRESS,OUT_OF_STOCK.
Boolean
R-FLD-7: префикс is/has/can опционально.
{
"active": true,
"hasDiscount": false,
"canCancel": true
}
Главное — единообразие в проекте. Либо active/enabled, либо isActive/isEnabled — выбираем один стиль и применяем везде.
null в PATCH body — удалить поле
R-FLD-6: семантика запроса.
PATCH /api/v1/orders/{id}
Content-Type: application/merge-patch+json
{ "comment": null }
null в body — команда удалить поле comment. Это JSON Merge Patch (RFC 7396).
Не путать с null в ответе — это разная семантика.
Формат ответов
R-RSP-1..6:
Единичный ресурс — плоский объект
{
"orderId": "550e8400-...",
"status": "CONFIRMED",
"totalAmount": 1500.00,
"createdAt": "2026-05-26T10:30:00Z",
"items": [
{ "itemId": "...", "productName": "Клавиатура", "quantity": 2, "price": 750.00 }
]
}
Без обёртки { data: ..., success: true }. Вложенные объекты и коллекции — допустимы внутри ресурса.
Коллекция — content + пагинация
{
"content": [
{ "orderId": "..." }
],
"page": 1,
"size": 20,
"totalElements": 243,
"totalPages": 13
}
content — стандартное имя поля с данными. Это не envelope, это структура пагинированного ответа — content всегда присутствует, плюс метаданные пагинации.
Создание — 201 + Location
HTTP/1.1 201 Created
Location: /api/v1/orders/550e8400-...
{
"orderId": "550e8400-...",
"status": "CREATED",
"totalAmount": 0,
"createdAt": "2026-05-26T10:30:00Z"
}
Обновление — 200 + ресурс
HTTP/1.1 200 OK
{
"orderId": "550e8400-...",
"status": "CONFIRMED",
"totalAmount": 1500.00,
"updatedAt": "2026-05-26T11:00:00Z"
}
Удаление — 204 No Content
HTTP/1.1 204 No Content
Пустое тело. Без { success: true } или подобных.
Action — 200 + обновлённый ресурс
POST /api/v1/orders/{id}/confirm
HTTP/1.1 200 OK
{
"orderId": "550e8400-...",
"status": "CONFIRMED",
"confirmedAt": "2026-05-26T11:00:00Z"
}
Для async action — 202 Accepted (см. Batch, async, локализация).
null в успешном ответе — запрещён
R-RSP-X1: критическое правило.
{
"orderId": "...",
"status": "CREATED",
"items": [],
"comment": "Доставить до 18:00"
}
Если поле не заполнено — отсутствует в JSON, не "discount": null. Это:
- Уменьшает размер ответа.
- Упрощает клиентский код (
if (data.discount)вместоif (data.discount && data.discount !== null)). - Защищает от ошибок типа
"discount": nullvs missing key.
В Spring настраивается:
@Bean
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json()
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
}
R-RSP-X2: пустые строки "" вместо отсутствия — тоже запрещены. "" ≠ «нет данных», это есть, но пусто.
R-RSP-X3: nullable: true в OpenAPI — запрещено. Поле либо есть, либо отсутствует. В OpenAPI:
OrderResponse:
type: object
required:
- orderId
- status
- items
properties:
orderId:
type: string
format: uuid
discount:
type: number
description: 'Отсутствует если скидки нет'
Поле discount не в required — может отсутствовать. nullable: true не нужен.
Envelope запрещён
R-RSP-X4: критическое правило.
// ✗ — envelope
{
"success": true,
"data": { "orderId": "...", "status": "CREATED" },
"error": null
}
// ✓ — плоский
{
"orderId": "...",
"status": "CREATED"
}
Почему envelope не нужен:
- HTTP-статус уже сообщает success vs error (
200vs4xx/5xx). - Ошибки — формат RFC 9457 (
type,title,detail,status), см. Ошибки. - Коллекции оборачиваются в
{ "content": [...] }— это структура пагинации, не envelope.
Envelope добавляет noise без выгоды. Клиент пишет response.data.orderId вместо response.orderId — лишний уровень.
Пустые коллекции
R-RSP-7:
{ "items": [] } ✓
{ "items": null } ✗
{ } ✗ (если items required)
[] означает «коллекция есть, пуста». null или отсутствие — двусмысленно.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
null в response 2xx | R-RSP-X1 | поле отсутствует |
"" для отсутствия | R-RSP-X2 | поле отсутствует |
nullable: true в OpenAPI | R-RSP-X3 | optional field |
{ success: true, data: ... } envelope | R-RSP-X4 | плоский объект |
customer_id snake_case | R-FLD-1 | customerId |
created_at | R-FLD-2 | createdAt (ISO 8601 string) |
status: "in_progress" | R-FLD-3 | "IN_PROGRESS" |
id: "..." без суффикса | R-FLD-5 | orderId, customerId |
{ items: null } | R-RSP-7 | { items: [] } |
2026-05-26 10:30:00 без T | R-FLD-2 | 2026-05-26T10:30:00Z |
Content-Type: text/json | R-RSP-1 | application/json |
Куда дальше
- REST API → JSON (нормативно) — формулировки.
- URL и ресурсы — HTTP-методы и codes.
- Query-параметры и пагинация — структура
contentдля коллекций. - Ошибки RFC 9457 — формат error response.
- Заголовки и трассировка —
Location,ETag. - Версионирование — добавление optional поля non-breaking.