JavaScriptのURLSearchParams.sort():クエリのキー順をそろえる
URLSearchParams.sort()でクエリのキー順をそろえる方法を解説。引数・戻り値、ブラウザーとNode.jsの実行手順、同名キーの順序維持、元データの変更や再エンコードの注意点を学べます。
目的:検索条件の並び順による文字列の違いを減らす
検索画面で作るpage=2&category=booksとcategory=books&page=2は、アプリケーションによっては同じ検索条件を表します。しかし、文字列として比較すると一致しません。検索結果のキャッシュキーやテストの期待値を作る際、条件を追加した順番によって結果が変わると扱いにくくなります。
URLSearchParams.sort()は、クエリのキーと値の組をキー順に並べ替えるメソッドです。値を手作業で分解して並べ直す必要がなく、同じキーが複数あるデータも保持できます。
この記事では、キーの順番をそろえたクエリ文字列を作る処理に絞って解説します。URL全体の同一性を判定したり、検索条件の意味を検証したりする処理は別に必要です。
動作環境と対応バージョン
ブラウザーとNode.jsの両方で利用できます。ブラウザー専用の画面操作は使わないため、この記事のJavaScriptコードは両環境で共通です。
- ブラウザーでは、現行のChrome、Edge、Firefox、Safariを対象にします。MDNは、2018年4月以降、主要ブラウザーを横断して利用できる機能としています。
- Node.jsでは、本文の実行環境として22系以降を想定します。外部パッケージやビルド処理は不要です。
- Node.js公式の履歴では、メソッド自体の追加はv7.7.0およびv6.13.0です。ただし、インポートせず使うグローバルの
URLSearchParamsはv10.0.0からです。
機能が追加された古いバージョンを、新しい開発環境として選ぶ必要はありません。導入履歴と実際に使う環境を分けて判断してください。対応状況はMDNのsort()、Node.jsの履歴は公式URL APIドキュメントで確認できます。
引数・戻り値と並べ替えの規則
呼び出し方はparams.sort()です。引数はありません。比較関数や降順指定を受け取るAPIではありません。
戻り値はundefinedです。新しいオブジェクトを返すのではなく、呼び出したURLSearchParams自身を書き換えます。文字列が必要なら、その後にparams.toString()を呼びます。
キー順と安定ソートを理解する
並べ替えはキーのUTF-16コード単位の値に従います。日本語の読み順や、人間が考える自然な数値順とは限りません。たとえば、キーがitem10とitem2なら、数字部分を数値として比較する処理は行われません。
また、同じキーの組どうしは、もとの相対順序を保ちます。これが安定ソートです。tag=z&tag=aは、並べ替え後もtag=z&tag=aの順です。値まで昇順になると考えないようにしてください。これらの規則と戻り値はMDNのメソッド仕様に記載されています。
自己完結したコードで実行する
次のコードはクエリ文字列を受け取り、そのキー順をそろえた文字列を返します。ネットワーク通信やファイルの読み書きは行いません。
(() => {
"use strict";
function sortQuery(query) {
if (typeof query !== "string") {
throw new TypeError("queryは文字列で指定してください");
}
const params = new URLSearchParams(query);
params.sort();
return params.toString();
}
const first = "tag=z&page=2&tag=a&category=books";
const second = "category=books&tag=z&tag=a&page=2";
const firstKey = sortQuery(first);
const secondKey = sortQuery(second);
console.log("整列後: " + firstKey);
console.log("一致: " + (firstKey === secondKey));
console.log("元の文字列: " + first);
console.log("空文字列: " + JSON.stringify(sortQuery("")));
try {
sortQuery(null);
} catch (error) {
console.log(error.name + ": " + error.message);
}
})();sortQueryの引数queryは、URL全体ではなくクエリ部分の文字列です。戻り値は、先頭の?を含まない文字列になります。関数内で新しいURLSearchParamsを作るため、呼び出し元の文字列は変わりません。
ブラウザーでの実行方法
通常のWebページを開き、開発者ツールのConsoleにコード全体を貼り付けて実行します。ページ内の要素には依存しません。コードを即時実行関数で囲んでいるので、同じConsoleで繰り返し実行しても、トップレベルの定数名が衝突しにくい形です。
Node.jsでの実行方法
コード全体をUTF-8のquery-sort.jsとして保存します。ターミナルでそのファイルがあるディレクトリーに移動し、次を実行してください。
node --version
node query-sort.js最初のコマンドで利用中のバージョンを確認できます。コードにはimportやrequireがなく、追加のパッケージ設定も不要です。
期待される出力
ブラウザーとNode.jsで、コードが出力する内容は次のとおりです。Consoleには別途、評価結果のundefinedが表示される場合があります。
整列後: category=books&page=2&tag=z&tag=a
一致: true
元の文字列: tag=z&page=2&tag=a&category=books
空文字列: ""
TypeError: queryは文字列で指定してください同名のtagは、値がz、aの順を保ちます。この例は同期処理なので、awaitやPromiseの拒否処理は必要ありません。入力の型が違う場合には関数側で例外を投げ、例ではtry...catchで受け取っています。出力は仕様に基づく期待値であり、実機での検証結果ではありません。
失敗例と制約
戻り値をつなげて呼び出さない
params.sort().toString()と書くと、undefinedに対してメソッドを呼び出すためTypeErrorになります。次のコードでは、その失敗を例外として確認できます。
(() => {
const params = new URLSearchParams("b=2&a=1");
try {
params.sort().toString();
} catch (error) {
console.log(error.name);
}
console.log(params.toString());
})();期待出力は次のとおりです。後続の呼び出しは失敗しても、sort()による変更はすでに終わっています。
TypeError
a=1&b=2既存のURLSearchParamsを変更したくない場合は、new URLSearchParams(original)でコピーしてから並べ替えます。
URL全体をクエリとして渡さない
new URLSearchParams("https://example.com/?b=2&a=1")は、URL全体を解析する書き方ではありません。URL文字列なら、まずnew URL(input)で解析してクエリ部分を取り出します。相対URLを解析する場合には基準URLも必要です。コンストラクターの扱いはMDNのURLSearchParams解説で確認できます。
並び替えだけでもエンコード表記が変わる
URLのsearchParamsは、そのURLと連動しています。次の例ではキーが1つしかありませんが、sort()後にクエリの表記が変わります。
(() => {
const url = new URL("https://example.com/?q=hello%20world~");
console.log(url.search);
url.searchParams.sort();
console.log(url.search);
})();期待出力は次のとおりです。
?q=hello%20world~
?q=hello+world%7Eこれは、URLSearchParamsのシリアライズで空白が+、チルダが%7Eになるためです。署名付きURLなど、元の文字列そのものが重要なデータには、仕様を確認せず適用しないでください。この挙動はMDNのsearchParamsとsearchの相互作用にも例があります。
活用事例と導入時の判断
アプリケーション内の検索結果キャッシュでは、キーを作る直前にクエリを整列すると、条件を追加した順番による文字列の違いを減らせます。Node.js公式も、キャッシュヒットを増やす用途を挙げています。Node.jsのsort()解説
ただし、クエリだけをキャッシュキーにすると、異なるパスの結果が衝突する可能性があります。実際には対象のパスや、結果に影響する利用者・言語などもキー設計に含めてください。これはアプリケーション側で判断する設計事項です。
テストでは、キーの順番だけが異なる入力を比較する前処理に使えます。一方、同名キーの値順が異なる入力は、整列後も一致するとは限りません。値順を無視してよいかは、APIの意味を確認する必要があります。
導入するなら、まず検索条件を作る処理の出口1か所に適用し、重複キーと空白を含むケースを確認するのが現実的です。すべてのURLを一律に変更するより、同一視してよい範囲を決めることで、意図した比較やキャッシュ設計につなげられます。
公式参考資料
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。