JSON Toolkit

Часті питання про командну співпрацю: контракти, конфігурація й тестові дані

Головне: Стиль найменування, формати часу, семантика null, форми переліків, формати конфігурації, організація тестових даних і конфлікти злиття — дванадцять компромісів, про які команди справді сперечаються.

Середній~12 хв читанняОпубліковано 2026-10-30Оновлено 2026-10-30
Обкладинка частих питань про командну співпрацю

Коротко: закріпіть це один раз у конвенції команди — обгортка {"data":…,"meta":…} із кодами статусу HTTP, один стиль найменування в усьому проєкті, часові мітки RFC 3339 з часовим поясом і рядкові переліки — потім еволюціонуйте кожне нове API проти того самого контракту, лише додаючи поля, ніколи не змінюючи чи видаляючи їх.

На ці питання немає єдиної правильної відповіді, але команда мусить мати єдину відповідь — інакше кожне нове API починає ту саму суперечку заново.

1. Контракти API

Яку структуру має повертати API?

Три поширені форми, кожна з ціною:

Форма Приклад Переваги Недоліки
Сирі дані [{...}, {...}] Просто Не може нести метадані
Фіксована обгортка {"code":0,"message":"ok","data":{...}} Уніфікована обробка помилок Бізнес-код дублює статус HTTP
Обгортка ресурсу {"data":{...},"meta":{...},"links":{...}} Розширювана, ясна семантика Ще один рівень вкладеності

Для внутрішніх бізнес-API надавайте перевагу «обгортці ресурсу + кодам статусу HTTP, що виражають результат»:

{
  "data": [{ "id": "1", "name": "示例" }],
  "meta": { "total": 128, "page": 2, "pageSize": 20 }
}

Для помилок використовуйте код статусу HTTP плюс структуроване тіло помилки, а не вкладайте ще один code:

{
  "error": {
    "type": "validation_failed",
    "message": "请求参数校验失败",
    "details": [
      { "path": "/email", "reason": "格式不正确" }
    ]
  }
}

Ключовий момент: коди статусу HTTP уже виражають успіх чи збій, тож бізнес-код зайвий. Коли дві системи співіснують, неузгодженості на кшталт «201, але code 500» майже неминучі.

Уникайте голих масивів. З масивом на верхньому рівні ви не можете долучити пагінацію, підсумки чи курсори, а додавання їх пізніше означає зміну кожного викликача.

Чи мають назви полів використовувати snake_case чи camelCase?

Оберіть одне, застосуйте в усьому проєкті й ніколи не змішуйте їх в одному документі JSON.

Стиль Поширена екосистема Приклад
camelCase JavaScript / TypeScript / Java userName, createdAt
snake_case Python / Ruby / Go (частково) / стовпці баз даних user_name, created_at
kebab-case Рідко; ключі з дефісами незручні для доступу user-name

Критерії:

  1. Хто споживає цей JSON: обирайте camelCase, коли домінує фронтенд, snake_case, коли домінують конвеєри даних і сервіси Python;
  2. Чи відображається він прямо на стовпці бази даних: snake_case заощаджує шар перетворення, коли структури таблиць передають напряму;
  3. Наступність наявних систем: тримайте те, що використовує наявний API; узгодженість краще за правильність.

Якщо справді треба конвертувати, робіть це на межі (шлюз або шар запитів фронтенду), а не розкидайте toCamelCase по бізнес-коду.

Який формат мають використовувати поля часу?

Використовуйте рядки RFC 3339 / ISO 8601 зі зміщенням часового поясу:

{
  "createdAt": "2026-10-30T11:37:57+08:00",
  "updatedAt": "2026-10-30T03:37:57Z"
}

Ключові моменти:

  • Включайте часовий пояс: 2026-10-30 11:37:57 без поясу неминуче зламається в сценаріях між часовими поясами;
  • Використовуйте Z або зміщення послідовно: обидва правильні, але не змішуйте їх в одному API;
  • Уникайте числових часових міток: чи це секунди чи мілісекунди й чи включені високосні секунди — кожна команда мусить перетлумачити це. Коли потрібна компактність, використовуйте ціле в мілісекундах, але закріпіть одиницю в документації.

Надавайте перевагу назвам полів із суфіксом At, як-от createdAt / updatedAt / deletedAt, щоб одразу було видно, що значення — момент часу, а не тривалість. Для тривалостей використовуйте timeoutMs, ttlSeconds — кладіть одиницю в назву.

Мають nulls бути null чи пропущеним полем?

Залежить від того, чи «це поле має сенс»:

Семантика Форма Примітки
Поле існує, але ще не має значення "name": null Явно каже «перевірено, і воно порожнє»
Поле не стосується цього ресурсу Пропустити Наприклад, звичайний користувач не повинен мати vipExpireAt
Часткове оновлення (PATCH) Пропуск = не змінювати Лише null тут означає «очистити»

Третій випадок найчастіше проґавлюють. У запиті PATCH:

{ "nickname": null }

це означає «очистити псевдонім», тоді як залишення nickname поза тілом запиту означає «не торкатися псевдоніма». Якщо трактувати обидва як «очистити», користувач, змінюючи email, стере свій псевдонім.

Ще раз: не використовуйте undefined. Це не валідне значення JSON, і JSON.stringify цілком відкидає ключ, тож поведінка збігається з «пропустити», тоді як код виглядає як «порожнє значення було передано явно».

Як найменувати булеві поля?

Використовуйте префікси is / has / can / should і уникайте заперечень.

{
  "isActive": true,
  "hasPassword": false,
  "canEdit": true
}

Три уроки:

  1. Без заперечень: isDisabled: false змушує читача один раз інвертувати його; isEnabled: true безпосередніше. Подвійне заперечення (isNotDisabled) — катастрофа;
  2. Не використовуйте числа для булевих значень: "enabled": 1 змушує кожного споживача один раз написати === 1;
  3. Не використовуйте рядки: "enable": "true" є істинним у JavaScript, і if (data.enable) також істинне для "false"; такі баги вкрай важко простежити.

Мають поля-переліки використовувати числа чи рядки?

Використовуйте рядки, якщо немає чіткого обмеження зберігання чи пропускної здатності.

{ "status": "active" }

Порівняно з { "status": 1 }, рядки виграють, бо:

  • Логи, перехоплення пакетів і бази даних безпосередньо читабельні без пошуку в таблиці відображення;
  • Додавання значення переліку не змінює сенс наявних значень (вставлення значення в середину числового переліку зсуває все);
  • Фронтенд не потребує синхронної підтримки відображення 1 → 'active'.

Ціна — кілька зайвих байтів, що після стиснення майже нехтовно.

Супутня практика: використовуйте нижній регістр і підкреслення для рядкових значень переліку (pending_review) і обмежуйте їх через enum у Schema, щоб невалідні значення відхилялися на етапі перевірки.

2. Конфігурація й тестові дані

Має конфігурація бути JSON, YAML чи TOML?

Обирайте за призначенням, а не за вподобанням:

Формат Добре для Погано для
JSON Програмного читання й запису, строго структурованих даних, перевірки Schema Частого ручного редагування (коментарі не дозволені)
YAML Глибоко вкладеної конфігурації розгортання (K8s) Рукописні відступи схильні до помилок; yes/no стають булевими
TOML Пласкої конфігурації залежностей та інструментів (Cargo.toml, pyproject.toml) Поганої читабельності глибокої вкладеності
JSONC Конфігурації редактора, що потребує коментарів (tsconfig.json) Перетинання меж систем

Поширена помилка в командах — зберігати бізнес-конфігурацію в YAML — неявне перетворення типів YAML (1.0 стає числом, NO стає булевим, 2026-10-30 стає датою) дає різні результати в різних парсерах.

Якщо конфігурацію треба строго перевіряти програмою, обирайте JSON + Schema і документуйте коментарі через поле _comment або окрему документацію.

Як організовувати тестові дані?

Організовуйте за принципом «один базовий зразок + мінімальні відмінності», а не копіюйте величезний JSON для кожного тест-кейсу.

fixtures/
├─ users/
│  ├─ base.json              # Базовий обʼєкт користувача
│  ├─ no-email.json          # Бракує лише email
│  └─ invalid-age.json       # Лише age поза діапазоном
└─ fixtures.ts               # Службові функції для завантаження й поверхневого злиття
import baseUser from './users/base.json';

export function makeUser(overrides: Partial<User> = {}): User {
  return { ...baseUser, ...overrides };
}

Так новому тест-кейсу треба написати лише відмінні поля, а зміна базового поля автоматично оновлює кожен випадок.

Три правила:

  1. Тестові дані мусять проходити перевірку Schema: базовий зразок сам має бути валідними даними;
  2. Не експортуйте реальні дані з виробництва: номери телефонів, номери ID і адреси — усе це персональна інформація;
  3. Тестові дані належать під контроль версій, але мусять лишатися малими: один файл понад кілька десятків КБ вартий обрізання чи генерації.

Як скоротити конфлікти злиття у файлах JSON?

JSON конфліктує легко: форматувальник переливає весь файл одразу, тож двоє людей, кожен змінюючи один рядок, конфліктують на всьому файлі.

Чотири тактики:

  1. Уніфікуйте форматування й долучіть його до .editorconfig: узгоджені відступи, закінчення рядків і завершальне переведення рядка уникають нерелевантних diff;
  2. Сортуйте ключі: домовтеся про абетковий порядок, щоб нове поле не зіштовхувалося з точкою вставлення іншого;
  3. Розділіть файли: розбийте один гігантський config.json на кілька файлів за доменом, і поверхня конфлікту природно зменшиться;
  4. Уникайте комітів «переформатувати все»: відокремте форматування від бізнес-змін у два коміти, щоб рев'ю лишалося читабельним.

Якщо команда використовує lock-файли (pnpm-lock.yaml, package-lock.json), не зливайте їх вручну; натомість перегенеруйте:

git checkout --theirs pnpm-lock.yaml
pnpm install

Як підтримувати сумісність версій?

Принцип — «додавати, не змінювати, не видаляти».

Зміна Сумісність Підхід
Додати необовʼязкове поле Сумісна Додайте; споживачі ігнорують невідомі поля
Додати обовʼязкове поле Несумісна Підніміть версію або надайте значення за замовчуванням
Видалити поле Несумісна Спершу позначте застарілим, стежте за викликачами, потім видаляйте
Змінити тип поля Несумісна Додайте нову назву поля й подвійний запис під час переходу
Змінити набір значень переліку Залежить Додавання значень сумісне; видалення — ні

Механізми підтримки:

  • Споживачі використовують «поблажливий розбір»: ігнорують невідомі поля й надають типові для відсутніх;
  • Сервер якийсь час тримає старі поля й повертає заголовок Deprecation у відповіді як підказку;
  • У Schema не позначайте нові поля як required, а застарілі позначайте deprecated: true.

Як перевіряти контракти в CI?

Трактуйте Schema як частину коду й перевіряйте її при кожному коміті.

# Перевірте зразки даних за Schema (через ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

Запускайте в CI три речі:

  1. Валідність самої Schema: чи оголошення $schema валідне й чи розвʼязується $ref;
  2. Чи відповідають тестові дані Schema: гарантує, що тестові дані не дрейфують;
  3. Чи контракт зворотно сумісний: порівняйте з попередньою версією й перевірте на видалені поля чи змінені типи.

Третій пункт потребує інструментарію (як-от openapi-diff чи json-schema-diff); без нього принаймні порівняйте зміни в масивах required і наборах ключів properties скриптом.

3. Стислий довідник

Питання Рекомендація
Структура відповіді {"data":..., "meta":...}, з кодами статусу HTTP для помилок
Стиль найменування camelCase (фронтенд на першому місці) або snake_case (дані на першому місці), однаково в усьому проєкті
Формат часу Рядки RFC 3339 з часовим поясом, назви полів з At у кінці
Nulls Використовуйте null для відсутності значення, пропускайте нерелевантні поля, пропуск у PATCH означає «не змінювати»
Найменування булевих Префікси is/has/can, без заперечень, без 0/1
Переліки Рядки, нижній регістр із підкресленнями, додайте enum у Schema
Формат конфігурації JSON для програмної перевірки, TOML або JSONC для ручного редагування
Тестові дані Один базовий зразок + перевизначення з мінімальними відмінностями, валідні за Schema
Конфлікти злиття Уніфікуйте форматування, сортуйте ключі, розділяйте файли
Сумісність версій Додавайте, не змінюйте, не видаляйте; позначайте застарілим перед видаленням