WinMergeでJSONデータを整形して比較する

# JSONデータの比較
2021/10/13 追記
[WinMerge 日本語版](https://winmergejp.bitbucket.io/)の2021/06/29 2.16.12-jp-3でPrettifyJSON展開プラグインが追加されたので、デフォルトでJSON整形比較が可能になりましたね！

---
[BeatSaber](https://beatsaber.com/)で遊ぶようになってからJSONファイルに触れる機会が増えたのですが、吐き出すツールによって未整形のJSONデータである場合があります。代表的なのは、譜面データの.datファイルですね。

Gitで作譜中の譜面データを管理しようと思ったけど、[MMA2](https://github.com/Assistant/MediocreMapAssistant2)で保存されるJSONデータが未整形なため[WinMerge](https://winmergejp.bitbucket.io/)で差分比較するのが困難になってます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92522/e18cbfac-5ce0-3406-9f72-e54d134e2ee3.png)
# WimMergeのプラグインで比較前整形
WinMergeにはプラグイン機能があって、標準でEXCEL等の比較には対応していますので、それを真似てJSONデータを整形してから比較できないか調べたら、[既に行っている人がいた](https://caro.blog.ss-blog.jp/2016-03-23)のですが、下記エラーが出て上手く動作しませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92522/a0a489af-2631-e7ed-49ea-2626a33a2a61.png)
エラーコードを調べるとSyntax errorっぽいのですが、WinMergeからのエラーメッセージが不親切でコードのどこがおかしいのか分からないです。VBScriptとJScriptに分けてCScriptで実行してみたりしましたが特にエラーも出ず、デバッグが難しそうでした。また、[64bit環境ではVBScriptからJScriptの関数呼び出しが出来ない](https://blog.goo.ne.jp/tkhs1732/e/a565bd5b289b1de72208e30449fb0268)様な情報もあるため、あきらめました。

別の方法を調査すると[コマンドラインで動く、JSON整形・集計ツールであるjq](https://stedolan.github.io/jq/)を見つけたので、これをVBScriptから外部コマンドで呼び出せば行けそうです。

```vb:CompareJsonFiles.sct
<scriptlet>
 
<implements type="Automation" id="dispatcher">
    <property name="PluginEvent">
        <get/>
    </property>
    <property name="PluginDescription">
        <get/>
    </property>
    <property name="PluginFileFilters">
        <get/>
    </property>
    <property name="PluginIsAutomatic">
        <get/>
    </property>
    <method name="UnpackFile"/>
    <method name="PackFile"/>
</implements>
 
<script language="VBS">
 
Option Explicit
 
Function get_PluginEvent()
    get_PluginEvent = "FILE_PACK_UNPACK"
End Function
 
Function get_PluginDescription()
    get_PluginDescription = "Json形式のファイルを整形し比較をする"
End Function
 
Function get_PluginFileFilters()
    get_PluginFileFilters = "\.json$;\.dat$"
End Function
 
Function get_PluginIsAutomatic()
    get_PluginIsAutomatic = True
End Function
 
Function UnpackFile(fileSrc, fileDst, pbChanged, pSubcode)
    Dim shell
    Dim command
    Dim jqPath
    jqPath = "C:\Program Files\WinMerge\MergePlugins\jq-win64.exe"
    Set shell = CreateObject("WScript.Shell")
    command = "cmd /c type """ & fileSrc & """ | """ & jqPath & """ > """ & fileDst & """"
    shell.Run command, 0, true
    pbChanged = True
    pSubcode = 0
    UnpackFile = True
End Function
 
Function PackFile(fileSrc, fileDst, pbChanged, pSubcode)
    PackFile = False
End Function
 
</script>
</scriptlet>
```
ダウンロードしたjqの実行ファイルはWinMergeのプラグインフォルダに置くことにしました。PATHが通ったところに置けばフルパス指定で実行しなくても大丈夫ですが、PC移行時とか忘れちゃうのでプラグインフォルダにCompareJsonFiles.sctと一緒に入れておくことにします。

ハマったところは、shell.Runの部分で `shell.Run(command,0,true)`だと 0x800a0414 コンパイルエラーになります。これもまたエラーコードしか出ないので非常にデバッグしづらいです。

拡張子フィルタがありますので`"\.json$;\.dat$"`の部分でJSONファイルに使われる拡張子を列挙すると良いです。BeatSaberで使うことを想定しているので、.datを追加しておきました。

# プラグインの使用方法
1. [jqの公式サイト](https://stedolan.github.io/jq/)からWindows(64bit)用のexeファイルをダウンロードして、WinMergeのプラグインフォルダに配置します。
2. 上記プラグインのスクリプトレットを`CompareJsonFiles.sct`で、同じくプラグインフォルダに配置します。その際にスクリプトの`jqPath = "C:\Program Files\WinMerge\MergePlugins\jq-win64.exe"`を環境に合わせて修正して下さい。なお、文字コードはSJISにして下さい。
3. WimMerge起動時のダイアログにあるファイル展開プラグインか、TortoiseGitで差分表示した場合などは起動後にメニューのプラグインから`展開プラグインで開く`を選択して、`CompareJsonFiles.sct`を選んで下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92522/edc2803a-fdfe-f9c9-7188-c44751ea2d4b.png)

4.上手く行けば、下記の様にJSONデータが整形されて表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92522/bb893cc6-04c4-55be-96eb-ec31ce9225c4.png)
5.WinMergeの設定で、指定`get_PluginFileFilters`拡張子の場合は自動で展開プラグインを適用させることもできますが、必要な場合のみ使用したいので私は手動で整形実行しています。

## WinMergeで整形後データを編集した場合
WinMergeで展開プラグインを使用したファイルを編集して保存しようとすると、以下の様なメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92522/cc4db6c6-27b2-9422-6473-c139308e35e6.png)
しかし、JSONファイルを整形しているだけなので、そのままＯＫで同名ファイルに上書き保存すれば問題ありません。

# その他
jqコマンドはJSONの整形だけでなくソートなどもできます。
例えばキー基準でソートを行い、構造的な比較をしたい場合は以下の様にスクリプトのjqコマンドを実行している部分を修正すれば可能です。

```
    command = "cmd /c type """ & fileSrc & """ | """ & jqPath & """ --sort-keys > """ & fileDst & """"
```

参考：jqでsortする https://qiita.com/yoza/items/c4c732779a5a10fd3b4c