きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

JavaでJsonを比較する(特定の項目を無視するやり方)

自分用記事。
Json比較に手間取ったので、未来の自分に役に立つはず。

なお可能であれば、assertThatのメソッドチェインに加えたいので、もしそっちで比較できることを知っていれば教えてほしいです…。

環境

  • org.skyscreamer.jsonassert
    • JSONAssertで読み込めなければ、絶対パス(?)で記載してください。
      org.skyscreamer.jsonassert.JSONAssert
      のように。
  • Java
  • Junit5

ソースコード

Repository
ソース

やりたいこと

JSONの構造での比較。
半角スペースや意図しない改行で失敗しないようにしたい。
構造上合っているのであれば、その順番は気にしたくない。

下記は一致しているとみなしたい。

{
    "a": "111",
    "b": "999"
}
{
     "b": "999",
     
    "a": "111"
}

通常比較

Escape文字が入って見づらいですが、こういうjsonを比較しようとしています。

expectJson
{
  "animal": "gorilla",
  "age": 18
}
actualJson
{
  "age": 18,
  "animal": "gorilla"
}
@Test
void test_00() throws JSONException {
    // language=json
    String expect = "{\"animal\": \"gorilla\",\n" +
            " \"age\": 18\n" +
            "}\n";

    // language=json
    String actual = "{ \"age\": 18," +
                    "\"animal\": \"gorilla\"\n" +
                    "}\n";

    // この第三引数は、配列項目を厳密に比較するか、比較しないかを設定します。
    // 厳密に比較したくなければ、JSONCompareMode.LENIENTを使用してください。
    JSONAssert.assertEquals(expect, actual, JSONCompareMode.STRICT);
}

ちゃんと、配列の順番が変わっていた場合に、NGであることを比較するソースも作ったので、良ければローカルで確認してください。
EnumSourceってあまり使わないと思ってましたが、こういう時の検証に便利ですね。

expectjson
{ 
  "animal": [
    {
      "id": 1,
      "name": "gorilla",
      "age": 18
    },
    {
      "id": 2,
      "name": "gorilla",
      "age": 22
    }
  ]
}
actualjson
{ 
  "animal": [
    {
      "age": 22,
      "name": "gorilla",
      "id": 2
    },
    {
      "id": 1,
      "name": "gorilla",
      "age": 18
    }
  ]
}
@EnumSource(value = JSONCompareMode.class)
@ParameterizedTest
void test_01(JSONCompareMode mode) throws JSONException {
    // language=json
    String expect = "{ \n" +
            "  " +
            "\"animal\": [\n" +
            "    {\n" +
            "      " +
            "\"id\": 1,\n" +
            "      \"name\": \"gorilla\",\n" +
            "      \"age\": 18\n" +
            "    },\n" +
            "    {\n" +
            "      \"id\": 2,\n" +
            "      \"name\": \"gorilla\",\n" +
            "      \"age\": 22\n" +
            "    }\n" +
            "  ]\n" +
            "}\n";

    // language=json
    String actual = "{ \n" +
            "  " +
            "\"animal\": [\n" +
            "    {\n" +
            "      \"age\": 22,\n" +
            "      \"name\": \"gorilla\",\n" +
            "      \"id\": 2\n" +
            "    },\n" +
            "    {\n" +
            "      \"id\": 1,\n" +
            "      \"name\": \"gorilla\",\n" +
            "      \"age\": 18\n" +
            "    }\n" +
            "  ]\n" +
            "}\n";

    JSONAssert.assertEquals(expect, actual, mode);
}

追加でやりたいこと

全ての項目を完全一致させたくないこともあると思います。
例えば、特定の項目はランダムな認証キーを発行してたり、応答時間を返してたりしていると、完全一致は難しくなります。

なので、一部比較項目として除外する方法を探しました。

特定の項目を完全無視するやり方

以下のソースであれば、time.insertTime, time.updateTimeを無視できます。

@Test
void test_01() throws JSONException {
    // language=json
    String expect = "{ \"time\" : {\n" +
            "  \"insertTime\": 201912010023,\n" +
            "  \"updateTime\": 202008111234\n" +
            "}," +
            "\"animal\": \"gorilla\"}\n";

    // language=json
    String actual = "{ \"time\" : {\n" +
            "  \"insertTime\": 201912010023,\n" +
            "  \"updateTime\": 999999999999\n" +
            "}," +
            "\"animal\": \"gorilla\"}\n";


    JSONAssert.assertEquals(expect, actual,
            new CustomComparator(JSONCompareMode.STRICT,
                    new Customization("time.insertTime", (o1, o2) -> true),
                    new Customization("time.updateTime", (o1, o2) -> true)
            )
    );
}

特定の項目を正規表現に沿っていなかったら無視

RegularExpressionValueMatcher を使用することで、正規表現に合っていなかった場合にエラーにさせることができます。

ただ、yyyyMMdd等で比較することはできなかったので、あんまり使えないんじゃないかな…。

なお、下記例ではinsertTimeが2019…なので、2020の正規表現に一致せず失敗します。

@Test
void test_02() throws JSONException {
    // language=json
    String expect = "{ \"time\" : {\n" +
            "  \"insertTime\": 201912010023,\n" +
            "  \"updateTime\": 202008111234\n" +
            "}," +
            "\"animal\": \"gorilla\"}\n";

    // language=json
    String actual = "{ \"time\" : {\n" +
            "  \"insertTime\": 201912010023,\n" +
            "  \"updateTime\": 202012300123\n" +
            "}," +
            "\"animal\": \"gorilla\"}\n";


    JSONAssert.assertEquals(expect, actual,
            new CustomComparator(JSONCompareMode.STRICT,
                    new Customization("time.insertTime", new RegularExpressionValueMatcher<>(".*2020.*")),
                    new Customization("time.updateTime", new RegularExpressionValueMatcher<>(".*2020.*"))
            )
    );

}