Выходит обновление плагина, он дописывает свои ключи в ваш конфиг и заодно меняет порядок остальных. Через сутки в дискорде жалобы, вы открываете бэкап и текущий конфиг в diff — а он красный целиком. Разбираемся, почему текстовый diff врёт на JSON, и что с этим можно сделать: сравнение с игнором порядка ключей, структурный дифф по путям, до шести файлов сразу и экспорт в JSON Patch. Читать далее
Пятница, сервер Rust, полтора десятка плагинов на Oxide. Выходит обновление одного из них — того самого, что отвечает за киты. Кладёте новую версию, плагин при загрузке дописывает в ваш конфиг новые ключи и заодно нормализует файл под себя: меняет порядок ключей, переформатирует, приводит секции к своему виду. Сервер поднимается, в консоли чисто, все довольны.
Через сутки в дискорде: «почему кит на кулдауне сутки вместо часа», «почему из ящиков сыпется не то», «у меня баланс обнулился». Где-то среди дописанного новая опция получила дефолтное значение, а старая, которую вы правили полгода назад, тихо перестала читаться.
Бэкап конфига у вас есть. Открываете два файла в текстовом diff — и он красный целиком. Не потому что всё изменилось, а потому что порядок ключей другой и отступы поехали. Отключаете пробелы — легче не стало. Дальше 1500 строк глазами в поисках трёх значений, которые реально отличаются.
Вот об этом статья. Точнее — о том, почему обычный diff на JSON врёт, и что я по этому поводу сделал в своём редакторе.
Почему текстовый diff врёт на JSONТекстовый diff честно показывает разницу в тексте. Проблема в том, что текст и данные — не одно и то же, а JSON вы правите как данные.
Что ломает сравнение:
Порядок ключей. Сериализатор поменял местами name и id — данные идентичны, diff красный.
Форматирование. Один файл на два пробела, второй на четыре, третий минифицирован. Diff: «изменено 100% файла».
Сдвиг блока. Добавили элемент в начало массива — всё, что ниже, «изменилось», потому что уехало на несколько строк.
Служебный мусор. updatedAt, version, lastModified меняются при каждом экспорте и шумят в каждом сравнении.
null против отсутствия ключа. Для текста это просто две разные строки, и по красно-зелёной простыне не понять, что именно перед вами.
В сумме вы отфильтровываете шум вручную. Каждый раз. На конфиге, где реальных изменений — три штуки.
Если коротко: diff ничего не знает про JSON. Ему нужно об этом рассказать.
Что я сделалЯ пишу CraftHub — кроссплатформенный редактор, который показывает JSON как таблицу: колонки типизированы, строки правятся в гриде или в текстовом режиме с подсветкой. Изначально он делался под игровые конфиги: друзья возились с Unity, и время уходило не на разработку, а на ручное ковыряние JSON.
В новой версии в нём появилось сравнение — две штуки, которые закрывают историю выше целиком.
Кнопка Changes: что я наредактировалПервое и самое частое: открыл конфиг, поправил десяток значений, отвлёкся, вернулся — и уже не помнишь, что именно менял. Раньше вариант был один: сохранить и посмотреть в git. Если файл вообще под git — а конфиги сервера, будем честны, чаще всего нет.
Кнопка Changes показывает документ в памяти против его последней сохранённой версии на диске.

Окно изменений
Работает и в табличном режиме, и в текстовом: дифф берётся из документа, а не из того редактора, который сейчас на экране. Правили таблицу — увидите изменения в JSON, ровно в том виде, в каком оно ляжет в файл.
Внутри:
side-by-side и unified — переключаются одним кликом;
подсветка на уровне слов — видно не «строка изменилась», а какой именно фрагмент внутри строки;
счётчики добавленного, удалённого и изменённого;
F7 / F8 — прыжки по изменениям, без скролла через две тысячи строк;
миниатюра справа — полоса со всеми изменениями по всему файлу, клик — переход;
свёрнутые куски — неизменённые участки схлопываются в «▾ показать ещё N строк».
Отдельная вкладка — структурное сравнение. Не строки, а пути JSON деревом: что добавлено, что удалено, что изменилось, где поменялся тип. Когда нужно не «прочитать дифф», а ответить на вопрос «какие поля вообще затронуты» — это быстрее в разы. Именно эта вкладка отвечает на вопрос из истории с китами: какая опция появилась, какая исчезла, у какой поменялся тип с числа на строку.
JSON Comparer: до шести документов сразуВторое окно — самостоятельный сравниватель, он вообще не связан с открытыми вкладками.

Сравниватель json
от двух до шести панелей; в каждую можно вставить текст, открыть файл или перетащить его мышью;
первая панель — эталон, все остальные сравниваются с ней, каждая пара получает свою вкладку с результатом;
панели переименовываются и переставляются: перетащил нужную влево — она стала эталоном;
любую панель можно заполнить из активной вкладки редактора — её текущим или последним сохранённым содержимым.
Шесть панелей — это не «потому что можно». Это ровно наш случай: бэкап до обновления, текущий конфиг, дефолтный конфиг из поставки плагина, и заодно конфиг с тестового сервера. Кидаете бэкап в первую панель — и сразу видите три сравнения против него.
Опции: что считать отличиемСамая полезная часть. Опции общие для всех сравнений и запоминаются между запусками:
игнорировать порядок ключей — снимает добрую половину ложных срабатываний;
игнорировать порядок элементов массива — когда массив по смыслу множество, а не список;
не учитывать регистр строк;
игнорировать null и пустые значения;
список игнорируемых путей — вписали $.updatedAt и $.meta.version, и они больше не мозолят глаза.
Включаете первый пункт — и та самая красная простыня из начала статьи сжимается до пяти строк, которые действительно изменились. Текстовый diff так не умеет в принципе: он не знает, что перед ним JSON.
Экспорт: чтобы приложить к тикетускопировать как unified patch или сохранить в .patch;
сохранить как JSON Patch (RFC 6902) — готовый машиночитаемый набор операций;
сохранить отчёт в Markdown или HTML — кинуть в issue разработчику плагина, в PR или коллеге, у которого CraftHub не стоит.
JSON Patch тут не для галочки: это стандарт, его понимают библиотеки в большинстве экосистем. Сравнили два конфига — получили применимый патч.
Дифф перед сохранениемМелочь, которая внезапно оказалась одной из самых удобных: перед записью на диск редактор показывает, что именно сейчас изменится, и ждёт подтверждения. Один раз это спасает от «а, я же не в том файле правил». Кого раздражает — галочка «больше не показывать» на месте.
Это не только про RustRust — просто самый наглядный пример: десятки JSON-конфигов, регулярные обновления плагинов, вайпы, два-три сервера, которые «одинаковые» ровно до момента, когда перестают быть одинаковыми.
Но та же боль ровно один в один воспроизводится там, где я с ней познакомился впервые:
Unity: экспорт из редактора, база предметов, баланс, локализация — JSON, который правят несколько человек и который надо сверять между ветками и билдами;
бэкенд: конфиги окружений, которые «должны отличаться только парой значений», а по факту разошлись за полгода;
API: два ответа сервиса до и после релиза, где надо понять, что поменялось в контракте, а не в порядке полей.
Если у вас есть свой случай, где diff врёт особенно нагло, — напишите в issue. Мне интересно, какие ещё правила сравнения имеет смысл добавить.
Что дальшеСледующая большая штука, которую я сейчас проектирую, — вычисляемые поля прямо в таблице: формулы в стиле Excel (=@[price] * @[qty]), протягивание вниз за уголок, пересчёт по графу зависимостей. При этом сами формулы не будут засорять ваш JSON: они лягут в отдельный файл рядом, а в документ попадёт только вычисленное значение.
Для тех же игровых конфигов это закрывает целый класс задач — пересчитать урон, цены, множители лута по всей таблице, не открывая калькулятор и не правя две сотни ячеек руками.
ПопробоватьРепозиторий: github.com/c3n9/CraftHub
Сборки под Windows, macOS и Linux: releases
Расширение для VS Code: маркетплейс (сравнение пока только в десктопной версии)
Проект я развиваю один и в свободное время, поэтому обратная связь работает буквально как приоритизация бэклога: почти всё крупное за последние релизы — проводник, вложенность, теперь сравнение — приехало из issue.
Если инструмент оказался полезным — звёздочка на GitHub реально помогает ему расти. А если что-то раздражает, тормозит или работает не так, как вы ожидали, — заводите issue, разберусь.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Свой VPN на Rust: как я спорил с сетью, TLS и самим собой | 0 | 8.78 | 27-06-2026 |
| 2 | [Перевод] Разработчик 2.0. Следующий уровень абстракции | 0 | 9.9 | 01-08-2026 |
| 3 | Как понять, что делает Rust-компилятор: визуализация AST, MIR и LLVM IR | 0 | 10.8 | 15-07-2026 |
| 4 | Ты не найдёшь эту ошибку. Потому что её нет в твоём коде. Как Self-describing API спасает от чужих рефакторингов | 5 | 8 | 07-07-2026 |
| 5 | Бенчмаркая ключи Dictionary: забыл IEquatable — получил ×5 и 96 байт на каждый поиск | 1 | 9.13 | 25-07-2026 |
| 6 | Странность августовского обновления Макса | 0 | 10 | 18-08-2026 |
| 7 | Рекомендации на онбординге Хабра стали обновляться чаще (и автоматически) | 3 | 6 | 13-07-2026 |
| 8 | Почему JSON на практике — быстрый формат для баз данных (если выкинуть лишние абстракции) | 5 | 7 | 07-07-2026 |
| 9 | Вторая копия Vue: как лишняя строка в lockfile повесила Chromium | 0 | 6.77 | 09-08-2026 |
| 10 | redb 3.6.0: багрепорт, который оказался в шести провайдерах сразу — плюс AS2/EDI и общий порт | 1 | 13.35 | 13-08-2026 |