JSON Toolkit

Вибір інструменту JSON diff: текстовий, структурний і візуальний

Головне: Текстовий diff знаходить, де файли різняться; структурний diff вирішує, чи вміст еквівалентний. Вибір правильного шару не дає сплутати порядок ключів з інцидентом.

Середній~9 хв читанняОпубліковано 2026-10-31Оновлено 2026-10-31
Обкладинка статті про вибір інструменту JSON diff

Коротко: текстовий diff виявляє зміну (спершу впорядкуйте ключі через jq -S), структурний diff вирішує еквівалентність (вирівнюйте масиви за первинним ключем), а візуалізація лише пояснює або переглядає її; у CI тримайте ці три шари окремо.

Два документи JSON, що виглядають по-різному, не означають, що їхній вміст різний. Обирайте правильний шар порівняння, перш ніж обирати інструмент.

1. Три види «diff» — не одне й те саме

Багато команд сприймають «порівняти JSON» як одну дію, а потім натрапляють на ті самі проблеми в різних сценаріях. Варто розділити три шари:

Шар Що порівнюється Що виявляє Типове використання
Текстовий diff Сирі байти й рядки Відступи, порядок ключів, пробіли, переведення рядків Дрейф конфігурації, рев'ю коду
Структурний diff Розібрані об'єкти й масиви Додані чи видалені ключі, змінені значення, зміни елементів масиву Регресія API, міграція даних
Візуальний diff Як подаються відмінності До якого рівня належить зміна, наскільки великий її радіус ураження Людське рев'ю, обговорення в команді

Текстовий diff не має залежностей, швидкий і природно інтегрується з Git. Його слабкість — він нічого не знає про семантику JSON. Два цілком еквівалентні документи засвітяться червоним і зеленим, якщо різниться порядок ключів чи відступи. Структурний diff — навпаки: спершу розбирає, потім порівнює, тож розпізнає, що значення лише перемістилося. Але йому потрібна правильна стратегія вирівнювання масивів; інакше одне вставлення помилково прочитається як повне переписування.

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

2. Текстовий diff: спершу приберіть порядок ключів через jq -S

Для швидкого погляду на два файли найдешевша комбінація — jq -S плюс diff -u. Прапорець -S рекурсивно сортує ключі об'єкта, нормалізуючи документи, чий вміст ідентичний, а порядок ключів різний:

jq -S . a.json > a.sorted.json
jq -S . b.json > b.sorted.json
diff -u a.sorted.json b.sorted.json

Це прибирає найпоширеніший хибний сигнал. RFC 8259 визначає об'єкти як невпорядковані колекції, тож {"a":1,"b":2} і {"b":2,"a":1} — те саме значення. Текстовий diff, однак, позначає кожен рядок як змінений. Сортування спершу робить червоні блоки набагато ближчими до справжніх проблем.

Зауважте, що jq -S сортує лише ключі; масиви він не сортує. Порядок масиву в JSON має значення, і [1,2] — не те саме значення, що [2,1], тож інструмент не ухвалить це рішення за вас. Якщо масив концептуально є невпорядкованою множиною, сортуйте його за первинним ключем самостійно перед порівнянням.

Є ще одна деталь, варта запам'ятовування: diff завершується з кодом 1, коли знаходить відмінності, але відсутній файл або помилка дозволу теж дають ненульовий код виходу. У CI не трактуйте кожен ненульовий код як «вміст змінився», інакше помилковий шлях буде повідомлено як зміну даних. Безпечніший шаблон спершу перевіряє файли, а потім обробляє відмінність:

if ! diff -u a.sorted.json b.sorted.json; then
  echo "JSON content differs" >&2
  exit 1
fi

delta робить текстовий diff читабельним

Сирий вивід diff -u працює, але довгі рядки й глибоко вкладений JSON втомлюють читати. delta — це фільтр diff, який додає підсвічування синтаксису, номери рядків і чіткіші фонові кольори:

diff -u a.sorted.json b.sorted.json | delta

Він починався як пейджер Git, тож може також перебрати git diff. Для людей, які читають десять конфігураційних diff на день, такі інструменти покращують швидкість читання, а не алгоритмічну точність. Інший поширений вибір — git diff --no-index --word-diff, який порівнює на рівні слів і добре працює для стисненого JSON, збереженого в дуже довгих рядках.

Одне застереження: прикрашання не усуває хибних відмінностей. Якщо порядок ключів не нормалізовано, delta просто робить шум красивішим. Правильний порядок завжди такий: спершу нормалізувати, потім прикрашати.

3. Структурний diff: jsondiffpatch розуміє семантику

Коли питання в тому, чи дві частини даних семантично еквівалентні, використовуйте структурний diff. Найпоширеніший варіант в екосистемі JavaScript — jsondiffpatch, який повертає об'єкт дельти, що описує зміни, а не текстові рядки з маркерами + і -:

import { create } from 'jsondiffpatch';

const instance = create();
const delta = instance.diff(
  { id: 1, tags: ['a', 'b'], enabled: true },
  { id: 1, tags: ['a', 'c'], enabled: false },
);

console.log(JSON.stringify(delta, null, 2));

Дельта розрізняє додані ключі, видалені ключі, замінені значення й переміщені елементи масивів. Це означає, що ви можете проходити дельту безпосередньо в перевірках: undefined, коли відмінності немає, або опис за шляхами, коли є. На відміну від текстового diff, її не бентежать відступи, порядок ключів чи числове написання, як-от 1e3 проти 1000.

Так само важливо, що вивід структурного diff може живити робочий процес патчів. jsondiffpatch надає patch і reverse, тож дельту можна застосувати до оригінального об'єкта й пізніше скасувати. Одне застереження: автоматично згенерована дельта — не той самий формат, що рукописний JSON Patch, і перетворення між ними потребує роботи. Про ширший підхід до порівняння дивіться JSON diff і порівняння.

4. Як усунути хибні відмінності через порядок ключів

Порядок ключів не просто дратує; він може дати неправильні висновки. Під час розслідування дотримуйтеся трьох кроків:

  1. Перед порівнянням рекурсивно нормалізуйте порядок ключів через jq -S або рівноцінний інструмент.
  2. Видаліть поля, що змінюються при кожному запуску, як-от timestamp, requestId і traceId.
  3. Підтвердьте відсутність повторюваних ключів. Повторювані ключі розв'язуються за принципом «перемагає останній» під час розбору, тож текстовий diff їх бачить, а структурний — ні.
jq -S 'del(.meta.timestamp, .meta.requestId)' a.json > /tmp/a.json
jq -S 'del(.meta.timestamp, .meta.requestId)' b.json > /tmp/b.json
diff -u /tmp/a.json /tmp/b.json

Цей потік добре пасує до дрейфу конфігурації. Пропустіть staging і продакшн через ту саму нормалізацію, і все, що залишиться, — це відмінність, яка справді потребує пояснення. Тримайте правила фільтрування у версіонованому скрипті, а не передруковуйте їх із пам'яті. Про бік редагування й написання скриптів дивіться Редактори, командний рядок і автоматизація.

5. Масиви: вирівнювати за індексом чи за id?

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

Вирівнювання Працює, коли Сильна сторона Ризик
За індексом Порядок масиву несе бізнес-сенс Просто, додаткові поля не потрібні Одне вставлення спричиняє каскад хибних сигналів
За первинним ключем Елементи — множина з унікальним id Виявляє вставлення, видалення й переміщення Потребує стабільного унікального ключа
Найдовша спільна підпослідовність Порядок важливий, але вставлення дозволені Стійкіше за чистий індекс Складніше пояснити, вища ціна

jsondiffpatch використовує objectHash, щоб оголосити первинний ключ, індексуючи елементи масиву перед порівнянням:

const instance = create({
  objectHash: (item: { id?: string; sku?: string }) => item.id ?? item.sku,
});

Зі звичайним jq ви можете спершу сортувати, а потім використати текстовий diff:

jq -S 'sort_by(.id)' items.json > items.sorted.json

Компроміс вирівнювання за id у тому, що позиційні зміни більше не рахуються як відмінності. Якщо бізнесу треба знати, що запис перемістився з позиції 2 на позицію 5, порівняйте ще й явне поле порядку; не покладайтеся лише на вміст множини.

6. Візуальний diff: шар, збудований для людей

Вивід структурного diff призначений для машин, а не для нарад. Коли треба пояснити зміну комусь іншому, візуалізація ефективніша:

  • Редактор diff у VS Code добрий для перегляду файл за файлом, із панелями поруч і вбудованим підсвічуванням.
  • Деревні переглядачі, як-от JSON Crack і JSON Hero, показують, до якого рівня належить зміна.
  • jsondiffpatch постачається з HTML-форматувальником, що відображає дельту як згортуваний багатий вигляд.

Цінність візуалізації — контекст. Текстовий diff каже, що змінився рядок 88; деревний вигляд каже, що рядок 88 належить data.items[3].price. Але візуалізація зазвичай завантажує весь документ у браузер або десктопний застосунок. Перш ніж відкривати виробниче навантаження, переконайтеся, чи інструмент обчислює локально, чи завантажує на сервер.

7. Три практики впровадження diff у CI

По-перше, нормалізуйте перед порівнянням. Закріпіть відступи, рекурсивно сортуйте ключі й фільтруйте мінливі поля до запуску diff. Інакше CI постійно падатиме через шум.

По-друге, обирайте вирівнювання за семантикою. Якщо масив — множина, вирівнюйте за первинним ключем. Якщо це послідовність, зберігайте вирівнювання за індексом. Не застосовуйте одне правило до кожного API.

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

# Вивести лише шляхи й значення, що різняться, а не весь документ
jq -S 'del(.timestamp)' actual.json > /tmp/actual.json
jq -S 'del(.timestamp)' expected.json > /tmp/expected.json
diff -u /tmp/expected.json /tmp/actual.json | head -n 80

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

8. Таблиця вибору інструменту

Сценарій Перший вибір Альтернатива Ключове нагадування
Поодиноке порівняння файлів diff у VS Code diff -u Спершу відформатуйте, щоб прибрати шум
Дрейф конфігурації jq -S + diff delta Фільтруйте мінливі поля
Регресія відповіді API jsondiffpatch deepdiff Вирівнюйте масиви за первинним ключем
Пояснення зміни команді JSON Crack / JSON Hero HTML-форматувальник Перевірте, чи дані завантажуються
Дуже великі файли Вилучення піддерева jq Перегляд jless Не завантажуйте все дерево в пам'ять
Перевірка перед комітом git diff --no-index pre-commit Обробляйте коди виходу явно

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

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

Чи jq -S змінює порядок масиву теж?

Ні. -S сортує лише ключі об'єкта й залишає масиви в оригінальному порядку. Масиви в JSON впорядковані, тож інструмент не перетворить [1,2] на [2,1]. Якщо масив концептуально є невпорядкованою множиною, сортуйте його явно через sort_by.

Чи 1 і 1.0 — те саме значення в структурному diff?

Більшість парсерів перетворюють обидва на те саме число, тож структурний diff зазвичай вважає їх рівними, тоді як текстовий diff позначає їх різними. Використовуйте структурний diff для вирішення еквівалентності, а текстовий — для вирішення, чи файли байт-у-байт ідентичні.

Чи можна використовувати вивід diff безпосередньо як JSON Patch?

Не безпосередньо. Інструмент diff повідомляє, що різниться, тоді як JSON Patch описує впорядкований список операцій; формати й семантика різняться. Автоматично згенеровані патчі також схильні посилатися на індекси масивів, які застарівають за конкурентності. Додайте операцію test перед застосуванням.

Як порівнювати файли JSON розміром у десятки мегабайт?

Спершу вилучте піддерево, яке вас цікавить, наприклад через jq '.data.items', потім сортуйте за первинним ключем і порівняйте. Не завантажуйте все дерево в пам'ять для рекурсивного diff. Дуже великі файли краще обробляти потоковим або блоковим порівнянням, а витрати пам'яті потребують окремої оцінки.

Чи можуть онлайн-інструменти diff злити мої дані?

Залежить від реалізації. Суто фронтенд-інструменти обчислюють у браузері й не завантажують дані; серверні інструменти надсилають вміст на бекенд, де він може потрапити в логи. Перш ніж порівнювати виробничу конфігурацію або клієнтські навантаження, підтвердьте, що сторінка обчислює локально, і за потреби перевірте один раз із вимкненою мережею.

Як обрати між текстовим і структурним diff?

Для швидкого погляду, де два файли різняться, сортуйте ключі через jq -S і запустіть diff -u. Щоб вирішити, чи два документи семантично еквівалентні, використовуйте структурний diff, як-от jsondiffpatch, і вирівнюйте масиви за первинним ключем. Текстовий diff не має залежностей і інтегрується з Git, тоді як структурний diff не бентежать відступи, порядок ключів чи числове написання.

Чому CI не має трактувати кожен ненульовий код виходу diff як відмінність?

diff завершується з кодом 1, коли знаходить відмінності, але відсутній файл або помилка дозволу теж повертають ненульовий код. Трактування кожного ненульового коду як «вміст змінився» повідомляє помилковий шлях як зміну даних. Безпечніший шаблон спершу перевіряє файли, а потім окремо обробляє відмінність.

Чи варто робити diff файлів у десятки мегабайт у браузері?

Ні. Спершу вилучте піддерево, яке вас цікавить, через jq, потім сортуйте за первинним ключем і порівняйте. Завантаження всього дерева в пам'ять для рекурсивного diff коштує набагато більше пам'яті й часу. Дуже великі файли краще обробляти потоковим або блоковим порівнянням.