Spring Bootでクエリパラメータの順序が異なるURIを比較する

はじめに

エキサイト株式会社 21卒 バックエンドエンジニアの山縣です。 UriComponentsのreplaceQueryParam()を使用してクエリパラメータの値を書き換えたときに、書き換える前と書き換えた後とでクエリパラメータの順序が異なってしまい、 単体テストで落ちてしまいました。 本記事では、クエリパラメータの順序が異なるURIを比較する方法について共有します。

概要

クエリパラメータの順序が異なる2つのURIを用意します。 このとき、uri1とuri2を比較するとfalseになります。

URI uri1 = URI.create("https://example.com?page=2&per_page=10");
URI uri2 = URI.create("https://example.com?per_page=10&page=2");

System.out.println(Objects.equals(uri1, uri2)); // → false

これは、URIがクエリパラメータを文字列で保持しており、equalsで比較したときにクエリパラメータは"page=2&per_page=10"と"per_page=10&page=2"の文字列比較を行っているため、falseになってしまうからです。 クエリパラメータの順序が異なったとしても同一のものとして比較したいときは、少し工夫する必要があります。

解決策

URIをUriComponentsに変換することで容易に比較することができるようになります。 UriComponentsではクエリパラメータをMap<K, List<V>>のラッパーであるMultiValueMapで保持しているため、クエリパラメータの順序が異なるものを比較したときにtrueを返します。

UriComponents u1 = UriComponentsBuilder.fromUri(uri1).build();
UriComponents u2 = UriComponentsBuilder.fromUri(uri2).build();

System.out.println(Objects.equals(u1, u2)); 
// → true

実際にUriComponentsのクエリパラメータの中身を見てみると、確かにMap形式で保持されていることが確認できます。

System.out.println(u1.getQueryParams());
// → {page=[2], per_page=[10]}

注意点

MultiValueMapは1つのキーに対して複数の値を扱うことができます。 そのため、クエリパラメータに同一のキーが複数指定されている場合、Listに変換されるため、falseを返す可能性があります。

URI uri3 = URI.create("https://example.com?page=2&per_page=10&page=3");
URI uri4 = URI.create("https://example.com?page=3&per_page=10&page=2");

UriComponents u3 = UriComponentsBuilder.fromUri(uri3).build();
UriComponents u4 = UriComponentsBuilder.fromUri(uri4).build();

System.out.println(u3.getQueryParams());
// → {page=[2, 3], per_page=[10]}

System.out.println(u4.getQueryParams());
// → {page=[3, 2], per_page=[10]}

System.out.println(Objects.equals(u3, u4));
// → false

したがって、同一のキーが複数指定される場合は注意しなくてはなりません

おわりに

UriComponentsを利用してクエリパラメータの順序が異なるURIを比較する方法についてまとめました。 クエリパラメータの順序が異なるURIを比較したいときって一度はあるのかなと思います。 URIをUriComponentsにして比較を行うことで、クエリパラメータ順序が異なっていたとしてもtrueを返すようになります。 「クエリパラメータの順序が異なるURIでも同じものとして扱いたい!」といった方に本記事が参考になれば幸いです。

参考

docs.oracle.com

spring.pleiades.io