エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

JSONの表現形式を変えずにキーでソートするsort-json-by-object-keyを作った

エムスリー エンジニアの岩本です。

3年前になりますが、弊社の滝安(@juntaki)が投稿した下記の記事にあるGolden file testingが現在もプロダクトに組み込まれてメンテナンスされています。

www.m3tech.blog

このGolden file testingを扱いやすくするためのツールを作ったので紹介します。

www.npmjs.com

f:id:cpw:20210913133445j:plain
ソートのイメージ

背景

アプリケーションの仕様を変更したときに数多くのGolden file testingが変更されてしまいます。 意図した変更だけがGolden fileに適用されていることを確認するためにGitのdiffを使って確認しようとしますが、 JSONを出力すると以前と異なる順番のキーで出力されてしまいます。

例えば下記のようなjsonが結果として出力されていたものがあった場合に、

{
    "a": "1234",
    "b": "5678",
    "c": "abcd",
    "d": false
}

1つキーを追加して再出力すると

{
    "d": false,
    "aa": 999,
    "c": "abcd",
    "b": "5678",
    "a": "1234"
}

というように出力されてしまい、これをdiffすると

@@ -1,6 +1,7 @@
 {
-  "a": "1234",
-  "b": "5678",
-  "c": "abcd",
    "d": false
+   "aa": 999,
+   "c": "abcd",
+   "b": "5678",
+   "a": "1234"
 }

というようにというようにどこが変更されたかがわかりづらくなってしまいます。 しかも、変更されるファイルは数十個にもなるため、確認漏れが発生する可能性も上がってしまいます。

これを下記のようなdiffにしたいです。

@@ -1,5 +1,6 @@
 {
   "a": "1234",
+  "aa": 999,
   "b": "5678",
   "c": "abcd",
   "d": false

jsondiffなどのツールを使えば確認可能ですが、できればGitlabなどのどのインタフェースでかんたんに確認できる方が嬉しいです。

jqで良いのでは?

なお、jqコマンドには-Sオプションがあり、オブジェクトのキーでソートしてくれます。しかし、これには問題があって一度値が評価されてしまうようで、下記のような変換が入ってしまいます。

$ cat test.json
{
  "xyz": 1.0,
  "defg": 3e7
}

$ jq -S < test.json
{
  "defg": 30000000,
  "xyz": 1
}

値の表現方法が変わってしまい、diffで差分がでてしまいます。 世の中にあるソートしてくれるツールは全部このような変換が入ってしまいました。

解決策

これらのツールが値の表現を変換してしまうのは一度実装しているプログラミング言語の型に変換してしまうことで表現が変わってしまうことが原因です。ですので、構文解析した後に解析結果の状態のままソートしてあげれば解決します。

調べてみるとnpmに構文木を造ってくれるライブラリjson-parse-astがあったので、これを利用させてもらいました。

あとは、この構文木を使ってソートするだけです。作ったツールで先程のjsonをソートすると下記のようになります。

$ sortjson < test.json
{
  "defg": 3e7,
  "xyz": 1.0
}

あとは出力されたjsonを予めソートしておくことで求めているdiffを得ることができるようになりました。

この記事を社内レビューに通して。。。

ここまでの記事を社内のレビューに通したところ、 滝安(@juntaki)から下記の指摘をもらいました。

つねに jq -S で変換したものをGolden fileにしたらいいような気がしたけど、どうですかね。

1.0が1になったりと明らかに型が変わってしまうから無理だろうとは思っていたのですが、ちゃんと調べていなかったのでこれを機に調べてみました。

{
  "num": 1.0
}

jq -Sに通すと

{
  "num": 1
}

になります。変換したものをgolden fileとしてテストに通すと変換が正しくされずテストが失敗することが確認できました。

ただもう少し踏み込むとテストの内部でGitHub - lukas-krecan/JsonUnit: Compare JSON in your Unit Testsを使っていました。このライブラリではすでにこういったことは想定されているようで、withToleranceを使えば解決できました。すごく残念。ライブラリが対応していなければ勝利できたのですが、最初の調査が甘かったです。反省。

今回のシステムに対してはjqで十分だったけど、場所によってはまだ使いみちはありそうなので良しとしておきます。

まとめ

  • jsonのdiffを取りやすくするため、JSONをキーでソートするツールを作った
  • 値の表現方法を変えないために構文木を利用した
  • 社内レビューがよく機能していてすごい

We're hiring!

エムスリーでは技術を一緒に楽しめるエンジニアを募集しています。 社員とカジュアルにお話もできますので、興味を持たれた方は下記よりお問い合わせください。

open.talentio.com

jobs.m3.com