Порівняти дві JSON-структури

порівнює дві JSON-структури та перелічує додавання, видалення та зміни, ключ за ключем

Навіщо порівнювати два JSON?

Порівняння двох JSON-структур регулярно трапляється у житті розробника. Відповідь API, що змінилася після оновлення. Файл конфігурації, що розходиться між двома середовищами. Експорт об'єктів, що повинен відповідати референсу. JSON diff відповідає саме на це запитання: що було додано, видалено або змінено, і де саме?

Порядковий diff (на кшталт git diff) часто недостатній. Якщо форматування відрізняється (пробіли, порядок ключів), текстовий diff сигналізує про сотні рядків, тоді як структура даних ідентична. Компаратор JSON працює зі структурою після парсингу, що усуває цей шум і виявляє лише семантичні розбіжності.

Формат diff, що виробляється інструментом

Для кожного розходження, інструмент повертає:

  • шлях у спрощеному форматі JSONPath, наприклад $.user.address[0].city;
  • тип серед added (додано праворуч), removed (присутній лише ліворуч), modified (різні значення);
  • ліве значення та/або праве значення залежно від типу.

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

Як алгоритм обходить дві структури

Алгоритм є рекурсивним. На кожному рівні він ідентифікує тип двох порівнюваних значень:

  • Якщо обидва є асоціативними об'єктами, він бере об'єднання ключів. Для кожного ключа він рекурсивно спускається або позначає added/removed, якщо ключ існує лише з одного боку.
  • Якщо обидва є впорядкованими масивами, він порівнює позицію за позицією. Відмінність на початку масиву може зміщувати все наступне, що виробляє докладний diff: це відоме обмеження наївного структурного diff.
  • Якщо типи відрізняються (об'єкт проти масиву, скаляр проти null), це позначається modified.
  • Якщо обидва значення є скалярами (рядок, число, булеве, null), достатньо простого суворого порівняння.

Порядок ключів у об'єкті не має значення: {"a": 1, "b": 2} і {"b": 2, "a": 1} виробляють порожній diff. Це відповідає семантиці JSON, де порядок не є значущим. Натомість порядок елементів масиву важливий: масив є впорядкованим за конструкцією.

Конкретний приклад

Ось дві версії об'єкту користувача:

// ліворуч
{
  "id": 42,
  "name": "Alice",
  "roles": ["admin", "editor"]
}

// праворуч
{
  "id": 42,
  "name": "Alice Martin",
  "roles": ["admin", "viewer"],
  "active": true
}

Вироблений diff:

  • $.name: modified, "Alice""Alice Martin"
  • $.roles[1]: modified, "editor""viewer"
  • $.active: added, true

Випадки використання

  • Diff середовищ: порівняти вивід endpoint у pre-prod та prod. Дуже корисно при міграції або оновленні кешу.
  • Аудит міграції: порівняти експорт до і після трансформації для перевірки, що жодне поле не було втрачено.
  • Регресія API: до і після модифікації, порівняти відповідь для ідентичного запиту. Порожній diff підтверджує відсутність регресії.
  • Синхронізація конфігурацій: порівняти composer.json між двома гілками, два файли .eslintrc, дві конфігурації Symfony.
  • Snapshot тести: замінити порядкове порівняння структурним у наборі інтеграційних тестів.

Обмеження структурного diff

Порівняння впорядкованих масивів є відомим обмеженням наївних структурних diff. Якщо вставити елемент на початку масиву, всі наступні позиції зміщуються і diff позначає кожне розходження як modification. Для таких випадків існують більш просунуті алгоритми (Myers, Patience, diff за природним ключем), але вони виходять за межі загального інструменту порівняння.

Diff також не пояснює, чому відбулася зміна: це констатація. Для аналізу регресії потрібно зіставити цю констатацію з комітами, розгортаннями та параметрами запиту.

JSON diff проти JSON Patch (RFC 6902)

Додатковим форматом є JSON Patch (RFC 6902). Він описує у вигляді операцій (add, remove, replace, move, copy, test), як трансформувати один JSON-документ в інший. Там де наш diff є констатацією (для людини), JSON Patch є рецептом (для машини). Обидва представлення еквівалентні для простих випадків, і JSON Patch корисний для RESTful API, що приймають часткові модифікації.

Часті запитання

Чи залежить diff від порядку ключів?

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

Як обробляти масиви, порядок яких не важливий?

Інструмент за замовчуванням вважає масиви впорядкованими (це JSON семантика). Якщо ви обробляєте набори, відсортуйте обидва масиви за природним ключем перед порівнянням або використовуйте спеціалізований сервіс порівняння, що враховує цю семантику.

В чому різниця з Git diff?

Git порівнює рядки тексту. Якщо відступи або порядок ключів відрізняються, Git diff є дуже докладним. JSON diff працює зі структурою після парсингу і сигналізує лише про розходження даних.

Чи приймається недійсний JSON?

Ні: якщо один з двох JSON не парситься, інструмент повертає помилку. Спочатку перевірте за допомогою нашого JSON validator.

Приклад запиту

curl -X POST https://cdrn.fr/api/v1/tools/json-diff/execute \
  -H "Content-Type: application/json" \
  -d '{"left":"...","right":"..."}'

Схема вхідних даних

Поле Тип Обов'язкове За замовчуванням
left text
right text

Точки доступу

  • GET https://cdrn.fr/api/v1/tools - перелічує всі доступні інструменти
  • GET https://cdrn.fr/api/v1/tools/json-diff - отримує схему цього інструменту
  • POST https://cdrn.fr/api/v1/tools/json-diff/execute - виконує цей інструмент з JSON-payload