URLSearchParamsでクエリを正しく扱う

URLSearchParamsの基本操作と、同じキーを複数持つクエリパラメータをオブジェクトから変換する方法をまとめます。

Published
2023年9月30日
Read in English

new URLSearchParams({ q: ["greeting", "foobar"] }).toString() が返すのは q=greeting%2Cfoobar です。配列が文字列へ暗黙変換され、2つの値が1つのカンマ区切り文字列になりました。これに気づかないまま検索条件をURLへ書き出すと、サーバー側では値が1件として届きます。

URLSearchParams の基本と、同じキーを複数持つクエリの扱い方をまとめます。Next.jsのApp Routerでも、useSearchParams を通じて読み取り専用のインターフェースを利用できます。

基本的なユースケース

URLSearchParams は、クエリ文字列、オブジェクト、キーと値のペアを持つ配列などから生成できます。

const empty = new URLSearchParams();

const fromString = new URLSearchParams("q=greeting");
const fromRecord = new URLSearchParams({ q: "greeting" });
const fromEntries = new URLSearchParams([["q", "greeting"]]);

Map のように、キーと値のペアを反復できるオブジェクトも渡せます。

const map = new Map();
map.set("q", "greeting");

const p = new URLSearchParams(map);

URLSearchParamsを利用する

toString は、保持しているクエリパラメータを文字列として返します。

const p = new URLSearchParams("q=greeting");

console.log(p.toString());
// 'q=greeting'

同じキーを複数指定すると、q=greeting&q=foobar のように出力されます。サーバーが q[]=greeting&q[]=foobar という形式を要求する場合は、キー自体を q[] にします。

const p = new URLSearchParams([
  ["q", "greeting"],
  ["q", "foobar"],
]);

p.toString();
// 'q=greeting&q=foobar'

値の追加、削除、置換には appenddeleteset を使います。get は指定したキーの最初の値、getAll はすべての値を返します。entries でキーと値を反復でき、sort でキー順に並べ替えることもできます。

同じキーが複数あるオブジェクトを変換する

Next.jsのPages Routerでは、useRouter が返す router.query からクエリを取得できます。同じキーが複数ある場合、値は次のような配列になります。

// router.query
{
  q: ["greeting", "foobar"];
}

このオブジェクトをそのまま渡すと、冒頭のカンマ区切り文字列になります。

const p = new URLSearchParams({ q: ["greeting", "foobar"] });

p.toString();
// 'q=greeting%2Cfoobar'

キーと値のペアを持つ配列へ変換すれば、同じキーを複数回追加できます。

const o = {
  q: ["greeting", "foobar"],
  r: "routing",
};

const entries: [string, string][] = [];
for (const [key, value] of Object.entries(o)) {
  if (typeof value === "string") {
    entries.push([key, value]);
  } else {
    value.forEach((val) => {
      entries.push([key, val]);
    });
  }
}

const params = new URLSearchParams(entries);

URL全体がわかる場合は、URL インスタンスの searchParams をそのまま利用できます。

const u = new URL("https://example.com?q=greeting&q=foobar&r=routing#ddd");
u.searchParams.toString();
// 'q=greeting&q=foobar&r=routing'

さいごに

URLを扱う標準APIは、URLSearchParamsURLURLPattern とすでに充実しています。文字列を手でつなぐ前に使えるものがないか確かめれば、エンコードや複数値をめぐる不具合を減らせます。

参考