У меня есть много сложных структур данных. Структуры сложные, но каждая структура отличается от предыдушей совсем чуть-чуть. Мне нужно наглядно показать все изменнеия, которые происходили с этими структурами.
Физически у меня есть много JSON файлов, каждый файл — это лог отправки данных по одной сущности в некую систему. Мне нужно понятно видеть что менялось между разными отправками. Данные в JSON файлах серелизованы в человекочитаемом формате и отсортированы.
Я хочу иметь возможность показывать разницу как в консоле, так и на веб странице.
И конечно же хочется решить эту задачу как можно дешевле — идеально, если бы не пришлось ничего самому писать, а можно было бы просто взять уже готовое решение и использовать его.
Мне было бы удобнее всего решить эту задачу с помощью какой-нибудь Perl библиотеки.
Для того чтобы проэксперементировать с решением этой задачи я создал два JSON файла one.json и two.json (вот скрипт с помощью которого я их сгенерировал).
Теперь у меня есть тестовые данные и можно приступать к решению задачи.
Самое простое что можно сделать — это diff этих двух файлов:
$ diff one.json two.json
9c9
< "a"
---
> "b"
47c47
< "g" : 7,
---
> "g" : 19,
$
Результат работы — очень мало вывода (это плюс), но вывод совершенно не наглядный. Непонятно какой конкретно элемент структуры меняется. Это решение не походит.
В поисках решениях нашел замечательную Perl библиотеку String::Diff. С ее помощью можно легко написать скрипт, который очень наглядно покажет в чем файлы различаются. Еще один его плюс — с его помощью можно создать как вывод для консоли, так и для html страницы.
Но к сожалению, он тоже не походит. Он показыват не только изменения, но и всю структуру целиком. Структура очень большая, на стандратном размере шрифта видно только часть. Можно уменьшить размер шрифта, тогда будет все, но читать это неудобно.
Так что для этой моей задачи эта библиотека не подходит, но для других задач, она прекрасно подойдет.
Есть замечательная Perl библиотека Test::Deep, которую часто используют при тестировании. Оказыается, в комплекте с этой библиотекой идет модуль Test::Deep::NoTest, который позволяет использовать те же функции в обыкновенной програме. Как пишут на stackoverflow там есть функция deep_diag(), которая показывает в чем конкретно различие.
Получился вот такой скрипт.
И такое решение тоже не подходит — скрипт выдает только информацю про первое расхождение, а мне хочется видеть полный diff. Причем, интересно, разные вывовы сркипта показывают разные проблемы:
$ perl c.pl
no match
Compared $data->{"one"}[6]
got : 'a'
expect : 'b'
$
$ perl c.pl
no match
Compared $data->{"two"}{"foo"}{"g"}
got : '7'
expect : '19'
$
Пошел взгялнул что на эту тему есть в javascript. Нашел библиотеку data-diff, написал скрипт, но он почему-то не работает:
$ node a.js
[]
(при этом есть взять json из примеров, то он выдает json с разницой).
То что он работает, это, конечно, проблема, но кроме этой проблемы эта библиотека мне не подходит и по другим причинам: судя по документации, в результате работы получается структура данных из которых не понятно что на что поменялось.
Perl библиотека Data::Comparator. В доке написано что он может показывать разницу, но из доки я не понял как это сделать, копать не стал. Сразу не понравилось что в доке у него есть пример:
print '$a and $b are alike\n';
А пример это явно неправильный (одиночные кавычки и \n вместе не дружат), да и плохо использовать переменные $a и $b (можно использовать только в sort).
После долгих поисков я все-таки нашел именно то что мне было нужно.
JSON::MergePatch это Perl библиотека, которая реализует RFC 7396 JSON Merge Patch.
Эта библиотека сразу получает много очков, так как это реализация стандарта, а не своя выдумка. Я раньше и не знал про RFC JSON Merge Patch, а это, оказыватеся, очень клевая штука. Это формат с помощью которого можно частично изменять данные (глагол PATCH в REST API). И diff, который передается в этом формате хочеть быть как можно ближе к тому как выглядит структура данных.
Вот скрипт, который использует эту библиотеку. И вот его вывод:
$ perl f.pl
{
"one" : [
0,
1,
2,
3,
4,
5,
"a"
],
"two" : {
"bar" : {},
"foo" : {
"g" : "7"
}
}
}
$
То что при измении массива, его нужно целиком передвать — это недостаток стандарта, а не библиотеки. Но для меня это не очень критично. В тех json-ах, с которыми мне нужно работать мало массивов.
Но то что тут отображается пустой массив для "bar", на мой взгляд, это баг в библиотеке. Но и это проблема для меня не критична.
Изначально, когда я писал условие задачи мне хотелось чтобы по выводу можно было понять и начальное, и конечное значение, которое менялось. RFC JSON Merge Patch это не позволяет. Но этот RFC во всем остальном мне прекрасно подходит, поэтому я пожертвую этой фичей и буду использовать его.
6 декабря 2015