Пятница, сервер 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: до шести документов сразу
Второе окно — самостоятельный сравниватель, он вообще не связан с открытыми вкладками.
-
от двух до шести панелей; в каждую можно вставить текст, открыть файл или перетащить его мышью;
-
первая панель — эталон, все остальные сравниваются с ней, каждая пара получает свою вкладку с результатом;
-
панели переименовываются и переставляются: перетащил нужную влево — она стала эталоном;
-
любую панель можно заполнить из активной вкладки редактора — её текущим или последним сохранённым содержимым.
Шесть панелей — это не «потому что можно». Это ровно наш случай: бэкап до обновления, текущий конфиг, дефолтный конфиг из поставки плагина, и заодно конфиг с тестового сервера. Кидаете бэкап в первую панель — и сразу видите три сравнения против него.
Опции: что считать отличием
Самая полезная часть. Опции общие для всех сравнений и запоминаются между запусками:
-
игнорировать порядок ключей — снимает добрую половину ложных срабатываний;
-
игнорировать порядок элементов массива — когда массив по смыслу множество, а не список;
-
не учитывать регистр строк;
-
игнорировать
nullи пустые значения; -
список игнорируемых путей — вписали
$.updatedAtи$.meta.version, и они больше не мозолят глаза.
Включаете первый пункт — и та самая красная простыня из начала статьи сжимается до пяти строк, которые действительно изменились. Текстовый diff так не умеет в принципе: он не знает, что перед ним JSON.
Экспорт: чтобы приложить к тикету
-
скопировать как unified patch или сохранить в
.patch; -
сохранить как JSON Patch (RFC 6902) — готовый машиночитаемый набор операций;
-
сохранить отчёт в Markdown или HTML — кинуть в issue разработчику плагина, в PR или коллеге, у которого CraftHub не стоит.
JSON Patch тут не для галочки: это стандарт, его понимают библиотеки в большинстве экосистем. Сравнили два конфига — получили применимый патч.
Дифф перед сохранением
Мелочь, которая внезапно оказалась одной из самых удобных: перед записью на диск редактор показывает, что именно сейчас изменится, и ждёт подтверждения. Один раз это спасает от «а, я же не в том файле правил». Кого раздражает — галочка «больше не показывать» на месте.
Это не только про Rust
Rust — просто самый наглядный пример: десятки JSON-конфигов, регулярные обновления плагинов, вайпы, два-три сервера, которые «одинаковые» ровно до момента, когда перестают быть одинаковыми.
Но та же боль ровно один в один воспроизводится там, где я с ней познакомился впервые:
-
Unity: экспорт из редактора, база предметов, баланс, локализация — JSON, который правят несколько человек и который надо сверять между ветками и билдами;
-
бэкенд: конфиги окружений, которые «должны отличаться только парой значений», а по факту разошлись за полгода;
-
API: два ответа сервиса до и после релиза, где надо понять, что поменялось в контракте, а не в порядке полей.
Если у вас есть свой случай, где diff врёт особенно нагло, — напишите в issue. Мне интересно, какие ещё правила сравнения имеет смысл добавить.
Что дальше
Следующая большая штука, которую я сейчас проектирую, — вычисляемые поля прямо в таблице: формулы в стиле Excel (=@[price] * @[qty]), протягивание вниз за уголок, пересчёт по графу зависимостей. При этом сами формулы не будут засорять ваш JSON: они лягут в отдельный файл рядом, а в документ попадёт только вычисленное значение.
Для тех же игровых конфигов это закрывает целый класс задач — пересчитать урон, цены, множители лута по всей таблице, не открывая калькулятор и не правя две сотни ячеек руками.
Попробовать
-
Репозиторий: github.com/c3n9/CraftHub
-
Сборки под Windows, macOS и Linux: releases
-
Расширение для VS Code: маркетплейс (сравнение пока только в десктопной версии)
Проект я развиваю один и в свободное время, поэтому обратная связь работает буквально как приоритизация бэклога: почти всё крупное за последние релизы — проводник, вложенность, теперь сравнение — приехало из issue.
Если инструмент оказался полезным — звёздочка на GitHub реально помогает ему расти. А если что-то раздражает, тормозит или работает не так, как вы ожидали, — заводите issue, разберусь.
ссылка на оригинал статьи https://habr.com/ru/articles/1068522/