Выбор библиотеки для просмотра diff структр данных
blog

Выбор библиотеки для просмотра diff структр данных

Постановка задачи

У меня есть много сложных структур данных. Структуры сложные, но каждая структура отличается от предыдушей совсем чуть-чуть. Мне нужно наглядно показать все изменнеия, которые происходили с этими структурами.

Физически у меня есть много JSON файлов, каждый файл — это лог отправки данных по одной сущности в некую систему. Мне нужно понятно видеть что менялось между разными отправками. Данные в JSON файлах серелизованы в человекочитаемом формате и отсортированы.

Я хочу иметь возможность показывать разницу как в консоле, так и на веб странице.

И конечно же хочется решить эту задачу как можно дешевле — идеально, если бы не пришлось ничего самому писать, а можно было бы просто взять уже готовое решение и использовать его.

Мне было бы удобнее всего решить эту задачу с помощью какой-нибудь Perl библиотеки.

Тестовые данные

Для того чтобы проэксперементировать с решением этой задачи я создал два JSON файла one.json и two.json (вот скрипт с помощью которого я их сгенерировал).

Теперь у меня есть тестовые данные и можно приступать к решению задачи.

diff

Самое простое что можно сделать — это diff этих двух файлов:

$ diff one.json two.json
9c9
<         "a"
---
>         "b"
47c47
<             "g" : 7,
---
>             "g" : 19,
$

Результат работы — очень мало вывода (это плюс), но вывод совершенно не наглядный. Непонятно какой конкретно элемент структуры меняется. Это решение не походит.

String::Diff

В поисках решениях нашел замечательную Perl библиотеку String::Diff. С ее помощью можно легко написать скрипт, который очень наглядно покажет в чем файлы различаются. Еще один его плюс — с его помощью можно создать как вывод для консоли, так и для html страницы.

Но к сожалению, он тоже не походит. Он показыват не только изменения, но и всю структуру целиком. Структура очень большая, на стандратном размере шрифта видно только часть. Можно уменьшить размер шрифта, тогда будет все, но читать это неудобно.

String::Diff shows part of json diff

String::Diff shows part of json diff

Так что для этой моей задачи эта библиотека не подходит, но для других задач, она прекрасно подойдет.

Test::Deep::NoTest

Есть замечательная 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'

$

data-diff

Пошел взгялнул что на эту тему есть в javascript. Нашел библиотеку data-diff, написал скрипт, но он почему-то не работает:

$ node a.js
[]

(при этом есть взять json из примеров, то он выдает json с разницой).

То что он работает, это, конечно, проблема, но кроме этой проблемы эта библиотека мне не подходит и по другим причинам: судя по документации, в результате работы получается структура данных из которых не понятно что на что поменялось.

Data::Comparator

Perl библиотека Data::Comparator. В доке написано что он может показывать разницу, но из доки я не понял как это сделать, копать не стал. Сразу не понравилось что в доке у него есть пример:

print '$a and $b are alike\n';

А пример это явно неправильный (одиночные кавычки и \n вместе не дружат), да и плохо использовать переменные $a и $b (можно использовать только в sort).

Скрипт

JSON::MergePatch

После долгих поисков я все-таки нашел именно то что мне было нужно.

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 во всем остальном мне прекрасно подходит, поэтому я пожертвую этой фичей и буду использовать его.

Иван Бессарабов
ivan@bessarabov.ru

6 декабря 2015

Edit this post on GitHub