JavaRush /Курсы /Java Server /Соглашения об именах JSON

Соглашения об именах JSON

Java Server
11 уровень , 3 лекция
Открыта

1. Имена полей JSON как контракт

Когда мы начинаем проектировать JSON, очень легко скатиться в настроение “да какая разница, как назвать поле, главное — данные же правильные”. Но в backend‑мире это примерно как сказать: “какая разница, как подписать провода в щитке, главное — электричество же есть”. Пока не надо чинить, кажется, что правда всё равно. А потом наступает момент “почему у нас искрит только по пятницам”.

Если говорить строго, имя поля в JSON — это часть API‑контракта. Клиент (Postman, фронтенд, другое приложение, даже ваш будущий Java‑клиент) читает не ваши мысли, а конкретные строки: externalId, status, items. Если вы переименовали externalId в external_id, клиент не “догадается”, что это то же самое. Он просто не найдёт поле. И не потому, что клиент тупой, а потому что контракт — это именно договорённость в символах.

Можно представить это так:

flowchart LR
    Client["Клиент (кто-то снаружи)"] -->|"JSON c полями"| Server["Сервер/приложение"]
    Server -->|"JSON c полями"| Client

В этом обмене “магии понимания” нет. Есть только совпадение ожиданий. Поэтому мы и вводим соглашения об именах: чтобы ожидания были стабильными, а не зависели от вкуса автора “сегодня я люблю snake_case, а завтра — CamelCase и драму”.

2. Единый стиль: camelCase и snake_case

Почти у каждого новичка есть внутренний вопрос: “А как правильно — externalId или external_id?”. Секрет в том, что в индустрии встречается и то, и другое, и обе стороны могут быть “правильными” — в зависимости от команды, экосистемы, стандартов компании и исторических причин. Проблема начинается не там, где вы выбрали “не тот” стиль, а там, где вы выбрали два стиля одновременно.

Давайте очень приземлённо сравним варианты в таблице — без войн и обид:

Стиль Пример Где чаще встречается Типичная проблема
camelCase externalId Java/Spring экосистема, многие JSON API Новички иногда пишут externalID (с двумя большими буквами) и получают “зоопарк”
snake_case external_id Python, часть публичных API, некоторые REST‑гайды В Java‑коде поля обычно camelCase, и без явного правила легко начинается путаница
UPPER_CASE STATUS почти нигде как JSON‑контракт выглядит как крик и быстро превращается в хаос

Для нашего учебного проекта ReadLater Starter мы будем держать camelCase в JSON, потому что:

  1. так проще совпадать с привычными Java‑именами полей в DTO и не плодить лишнюю “ручную работу на перевод”;
  2. это делает примеры понятнее в начале пути (мы и так учим много нового).

Вот пример “нормального” ответа одного элемента списка чтения в этом стиле:

{
  "id": 1,
  "title": "Clean Code",
  "author": "Robert C. Martin",
  "status": "PLANNED",
  "externalId": "OL12345M",
  "comment": "Найти бумажное издание"
}

А вот пример того, как выглядит контракт, если стиль гуляет по полям как кот по клавиатуре:

{
  "id": 1,
  "bookTitle": "Clean Code",
  "external_id": "OL12345M",
  "STATUS": "PLANNED"
}

Технически это “тоже JSON”. Но контрактом это становится плохо: клиенту надо помнить три разных правила именования одновременно, и каждое новое поле будет “лотереей”.

3. Один смысл — одно имя: словарь ReadLater

Когда стиль (camelCase) выбран, следующая боль — синонимы. Новички часто называют одно и то же разными словами: bookId, externalId, catalogId, idFromProvider. Или state и status. Или list и items. А потом сами же не могут понять, где “настоящее поле”, а где “почти то же самое, но вроде другое”.

Чтобы этого избежать, полезно завести маленький словарь проекта: набор слов, которые мы используем стабильно. Это не бюрократия, а защита мозга от перегрева. Для ReadLater Starter можно договориться так:

Смысл Имя поля Пример Комментарий
Внутренний идентификатор элемента списка чтения id 42 Появляется в response, обычно не нужен в create‑request
Идентификатор книги во внешнем каталоге externalId "OL12345M" Это другая ось идентичности, не путать с id
Название книги title "Clean Code" Не bookTitle и не name, если мы договорились про title
Автор author "Robert C. Martin" В простом курсе достаточно строки
Статус чтения status "PLANNED" Лучше одно слово, чем state, readingState, bookStatus вперемешку
Комментарий пользователя comment "Купить на бумаге" Необязательное поле
Элементы списка в ответе коллекции items [ ... ] Плюральное имя для массива
Количество элементов count 3 Счётчик рядом со списком

Смысл этой таблицы в том, что мы не пытаемся “придумать идеальные названия на века”. Мы просто выбираем один термин на один смысл и держим его. Это экономит время и нервы сильнее, чем кажется.

Теперь пример на Java‑стороне (пока без сериализации, просто форма DTO). Сравним хорошую и плохую ситуацию.

Плохая: в одном DTO “id”, в другом “bookId”, в третьем “externalID” — и всё про одно и то же.

class BadReadingItemResponse {
    long id;            // внутренний идентификатор элемента в нашем сервисе
    String bookId;      // непонятно: это внутренний id или внешний?
    String externalID;  // другое написание "внешнего id": ломает единый словарь
}

Хорошая: id — внутренний, externalId — внешний. И везде одинаково.

class ReadingItemResponse {
    long id;           // внутренний id элемента (генерируется нашим сервисом)
    String externalId; // внешний идентификатор из каталога (это другой смысл, не путать с id)
    String title;      // название книги
}

Да, это “просто названия”. Но это те самые названия, которые потом будут в JSON и которые увидит любой клиент.

4. Имена списков: items и count

Когда вы впервые делаете endpoint “получить список”, есть соблазн вернуть просто массив. Типа “чего усложнять”. И иногда это действительно нормально. Но как только появляется необходимость вернуть рядом с массивом хоть что‑то ещё (например, count), начинается “а куда теперь это приклеить”.

Поэтому мы в проекте заранее привыкаем к простой форме: обёртка списка. И тут снова важны имена: чаще всего вы встретите items (элементы) и count (их количество). Да, звучит банально. И это хорошо: банально = предсказуемо.

Хороший ответ для списка reading list:

{
  "items": [
    {
      "id": 1,
      "title": "Clean Code",
      "author": "Robert C. Martin",
      "status": "PLANNED",
      "externalId": "OL12345M",
      "comment": null
    }
  ],
  "count": 1
}

Плохой ответ — не потому что “не по стандарту”, а потому что клиенту приходится гадать, что такое data, и почему рядом totalCount, а не count, и почему элементы называются list, а не items:

{
  "data": [
    { "id": 1, "title": "Clean Code" }
  ],
  "totalCount": 1
}

Можно ли так делать? Можно. Но тогда это тоже должно стать вашим соглашением. Проблема в том, что новички часто делают так: один endpoint возвращает items + count, другой data + totalCount, третий вообще “просто массив”, а четвёртый — result. И вот это уже не контракт, а квест.

На уровне DTO это выглядит очень просто:

class ReadingListResponse {
    ReadingItemResponse[] items; // элементы списка (то, что клиент будет перебирать)
    int count;                   // количество элементов (удобно для UI и пагинации)
}

Обратите внимание: даже в Java‑коде полезно держать те же имена, что и в JSON. Не потому что “так всегда”, а потому что в учебном проекте это снижает количество движущихся частей.

5. Имена DTO по ролям

Новички часто страдают от того, что “в проекте много классов, я путаюсь”. И это нормально: DTO действительно плодятся. Но путаница почти всегда не из‑за количества классов, а из‑за того, что их названия не подсказывают роль. Когда вы видите класс Data, вы не понимаете ничего. Когда вы видите CreateReadingItemRequest, мозг хотя бы понимает: “это вход на создание”.

Здесь полезно держать простое соглашение: в названии DTO должно быть видно направление и сценарий. Направление мы будем показывать суффиксами Request и Response. Сценарий — глаголом или контекстом: Create, Update, UpdateStatus, ReadingItem, ReadingList.

Плохой пример (вроде бы “коротко”, но непонятно):

class ItemDto { }   // непонятно: это request или response? для какого сценария?
class ItemDto2 { }  // "2" не объясняет смысл, это просто счётчик
class Payload { }   // payload чего именно? какого эндпоинта?

Хороший пример (чуть длиннее, но зато яснее):

class CreateReadingItemRequest { }  // вход на создание
class UpdateReadingItemRequest { }  // вход на обновление
class UpdateStatusRequest { }       // вход на изменение статуса (отдельный сценарий)

class ReadingItemResponse { }       // выход: один элемент
class ReadingListResponse { }       // выход: коллекция/список

Заметьте, мы здесь не “играем в чистую архитектуру” и не делаем десять уровней абстракций. Мы просто делаем так, чтобы человек, открыв файл, сразу понимал: это вход или выход, и для какого действия.

И ещё тонкость: если два DTO совпадают по полям, это не значит, что их надо склеить. Совпадение полей — случайность, а назначение — смысл. Например, CreateReadingItemRequest и UpdateReadingItemRequest могут быть одинаковыми по набору полей, но семантически это разные операции. Поэтому даже одинаковая форма не отменяет разные имена.

6. Переименование полей и breaking change

Переименование поля — одна из самых обманчивых правок в backend‑мире. Внутри кода вы привыкли: “ну переименовал переменную — IDE всё обновила, тесты прошли, живём”. Но JSON‑контракт — это не “внутри проекта”. Это наружу. IDE клиента ваши переименования не видит. И если контракт уже используется кем‑то ещё, переименование — почти всегда breaking change.

Чтобы не превращать лекцию в курс про версионирование API (это отдельная история), держим простую мысль: если поле уже опубликовано в контракте и клиент на него рассчитывает, то “просто переименовать” нельзя без последствий.

Полезно увидеть это на примере. Представим, что сначала мы выдавали ответ так:

{
  "externalId": "OL12345M",
  "title": "Clean Code"
}

А потом кто‑то решил “сделаем красиво, как в питоне” и поменял на:

{
  "external_id": "OL12345M",
  "title": "Clean Code"
}

С точки зрения сервера всё ок: данные есть. Но клиент, который искал externalId, теперь его не найдёт. В лучшем случае он покажет пустое поле. В худшем — упадёт с ошибкой, и пользователь скажет “ваш сервис сломан”. И формально он будет прав: контракт изменился.

Есть изменения, которые обычно менее болезненны. Например, добавить новое необязательное поле (которое клиент может игнорировать) обычно проще, чем переименовать существующее. Но мы пока держимся на базовом уровне: имена полей — это обещание, и менять обещание надо осторожно.

7. Типичные ошибки при именовании JSON

Ошибка №1: смешивание стилей именования в одном и том же проекте.
Самая частая история: часть DTO написали в camelCase, потом кто‑то добавил поле в стиле snake_case, потом в ответе внезапно появилось STATUS заглавными буквами, “потому что так заметнее”. В итоге клиент читает контракт как ребус. Правило простое: выбираем один стиль и держим его везде.

Ошибка №2: несколько названий для одного смысла (синонимы).
Сегодня вы называете внешний идентификатор externalId, завтра — catalogId, послезавтра — bookId. Каждый раз кажется, что “ну вроде понятно”. Но через неделю вы сами перестаёте понимать, это три разных поля или одно и то же. Спасает мини‑словарь проекта: один смысл — одно имя.

Ошибка №3: “универсальные” поля типа data, info, result, которые ничего не говорят клиенту.
Иногда кажется, что data — это удобно, ведь “там данные”. Но API‑контракт читают люди и программы. items говорит: “это элементы списка”. count говорит: “это количество”. data говорит: “ну… что‑то”. Чем конкретнее имя, тем меньше лишних вопросов у клиента.

Ошибка №4: случайные сокращения и странные аббревиатуры.
extId, authNm, ttl выглядят “компактно”, но читаются как шифр. В учебном проекте мы ценим ясность выше экономии двух символов. Если поле реально часто используется, вы всё равно будете его писать в коде много раз, и понятное имя окупается быстрее, чем кажется.

Ошибка №5: переименование полей “для красоты” без понимания последствий.
Внутри Java‑кода переименование обычно безопасно: IDE обновит ссылки. Внешний JSON‑контракт так не работает. Если поле уже “вышло наружу”, любое переименование может сломать клиентов. Поэтому прежде чем менять имя, задайте себе вопрос: “Кто это уже использует и что у них произойдёт?”

1
Задача
Java Server, 11 уровень, 3 лекция
Недоступна
Имена полей для детальной карточки книги
Имена полей для детальной карточки книги
1
Задача
Java Server, 11 уровень, 3 лекция
Недоступна
Имена полей для элемента и списка
Имена полей для элемента и списка
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ