「URL デコード」で何を取り戻したいのか
URL デコードは、URL の中で「%E3%81%82」みたいに変換されてしまった文字を、人間が読める元の文字列に戻すことです。
エンコードは「安全に送るための変換」。
デコードは「受け取ったあとに元に戻す変換」。
検索画面の ?q=%E6%A4%9C%E7%B4%A2 を「検索」という文字列に戻したり、A%26B を A&B に戻したりするのが、URL デコードの役目です。
JavaScript の 2 つの基本関数:decodeURI と decodeURIComponent
まずは名前と役割を対応させる
エンコードと同じく、デコードにも 2 種類あります。
decodeURI("https://example.com/%E6%A4%9C%E7%B4%A2?q=%E3%83%86%E3%82%B9%E3%83%88");
decodeURIComponent("%E6%A4%9C%E7%B4%A2");
JavaScriptざっくり対応はこうです。
- encodeURI ↔ decodeURI
→ URL 全体を扱うペア - encodeURIComponent ↔ decodeURIComponent
→ URL の一部(パラメータ値など)を扱うペア
「エンコードに何を使ったか」と同じものをデコード側でも使う、これが一番大事なルールです。
decodeURIComponent で「パラメータ値」を元に戻す
クエリパラメータを自分でパースするイメージ
例えば、こんな URL があったとします。
/ search?q=JavaScript%20%E5%85%A5%E9%96%80
q の値を取り出して、元の文字列に戻したい。
const qs = "q=JavaScript%20%E5%85%A5%E9%96%80";
const [key, rawValue] = qs.split("=");
// key: "q"
// rawValue: "JavaScript%20%E5%85%A5%E9%96%80"
const value = decodeURIComponent(rawValue);
// "JavaScript 入門"
JavaScriptここで重要なのは、rawValue に対して decodeURIComponent を使うことです。
エンコード側で
encodeURIComponent("JavaScript 入門");
JavaScriptを使っているので、デコード側も decodeURIComponent で対になる、というイメージです。
もし、ここで decodeURI を使うとどうなるか。
decodeURI("JavaScript%20%E5%85%A5%E9%96%80");
// "JavaScript 入門"(このケースではたまたま同じ結果)
JavaScriptこの例ではたまたま同じですが、& や = などが絡むと挙動が変わるので、
「パラメータ値には decodeURIComponent」と覚えておくのが安全です。
decodeURI で「URL 全体」を元に戻す
すでに URL として組み立てられたものを読む
例えば、ログにこんな URL が残っていたとします。
https://example.com/%E6%A4%9C%E7%B4%A2?q=%E3%83%86%E3%82%B9%E3%83%88
これを人間が読める形にしたいときは、decodeURI を使います。
const encodedUrl =
"https://example.com/%E6%A4%9C%E7%B4%A2?q=%E3%83%86%E3%82%B9%E3%83%88";
const decodedUrl = decodeURI(encodedUrl);
// "https://example.com/検索?q=テスト"
JavaScriptここで decodeURI がやっているのは、
%E6%A4%9C%E7%B4%A2→検索%E3%83%86%E3%82%B9%E3%83%88→テスト- ただし、
:や/や?や=はそのまま
という変換です。
もし、ここで decodeURIComponent を使うとどうなるか。
decodeURIComponent(
"https://example.com/%E6%A4%9C%E7%B4%A2?q=%E3%83%86%E3%82%B9%E3%83%88"
);
// "https://example.com/検索?q=テスト"
JavaScriptこの例では同じ結果になりますが、
本来 decodeURIComponent は「URL 全体」ではなく「一部」に使うものなので、
「URL 全体を読むときは decodeURI」と覚えておくと混乱しにくいです。
実務でよくやる「クエリ文字列 → オブジェクト」ユーティリティ
location.search をパースして、値をデコードする
ブラウザで location.search を見ると、こんな文字列が取れます。
?q=JavaScript%20%E5%85%A5%E9%96%80&page=2
これを
{ q: "JavaScript 入門", page: "2" }
JavaScriptのようなオブジェクトにしたい、というのはよくある要件です。
function parseQueryString(qs) {
const result = {};
if (!qs) return result;
const query = qs.startsWith("?") ? qs.slice(1) : qs;
if (query === "") return result;
const pairs = query.split("&");
for (const pair of pairs) {
if (!pair) continue;
const [rawKey, rawValue = ""] = pair.split("=");
const key = decodeURIComponent(rawKey);
const value = decodeURIComponent(rawValue);
result[key] = value;
}
return result;
}
JavaScriptここでの重要ポイントは、
keyもvalueも decodeURIComponent するa=b=cのようなケースでも、最初の=だけで分割する(split("=")の最初の2つを使う)?を外してから処理する
というところです。
実際に使うとこうなります。
parseQueryString("?q=JavaScript%20%E5%85%A5%E9%96%80&page=2");
// { q: "JavaScript 入門", page: "2" }
parseQueryString("tag=A%26B");
// { tag: "A&B" }
JavaScriptエンコード側で encodeURIComponent を使っていれば、
デコード側で decodeURIComponent を使うことで、きれいに元に戻せます。
設計として意識してほしいこと
「エンコードとデコードは必ずペアで考える」
URL 周りのバグの多くは、
- エンコードしていないのにデコードしようとする
- encodeURI したものを decodeURIComponent で戻そうとする
- そもそもどこでエンコードしたか分からない
といった「ペア関係の崩れ」から生まれます。
なので、
- パラメータ値 →
encodeURIComponent/decodeURIComponent - URL 全体 →
encodeURI/decodeURI
という対応を、プロジェクトのルールとして決めておくと、かなり安定します。
「何でもかんでも decode しない」
受け取った文字列に対して、なんとなく decodeURIComponent をかけるのは危険です。
- もともとエンコードされていない文字列に decode をかける
%の後ろが不正な形式でエラーになる
といったことが起きます。
「これは URL から来たクエリ文字列だから decode する」
「これはすでに人間が読む用の文字列だから decode しない」
という線引きを、コードの中で意識しておくと、バグを減らせます。
ちょっとだけ手を動かしてみる
コンソールで、次の順番で試してみてください。
decodeURIComponent("JavaScript%20%E5%85%A5%E9%96%80");
decodeURIComponent("A%26B");
decodeURI("https://example.com/%E6%A4%9C%E7%B4%A2?q=%E3%83%86%E3%82%B9%E3%83%88");
parseQueryString("?q=JavaScript%20%E5%85%A5%E9%96%80&page=2");
JavaScript「どの部分がどう元に戻るか」
「エンコードされた %xx がどう文字に変わるか」
を、自分の目で確認してみてください。
その感覚がつかめると、
あなたの URL 周りのコードは、「なんとなく encode/decode を呼んでいる状態」から、
「どこでエンコードし、どこでデコードするかを意識した“業務レベルの URL ユーティリティ”」に変わっていきます。
