URLの %E6%9D%B1 のような文字列を読む形へ戻すのがデコードです。反対に、日本語や記号をURLの中へ収めるために変換するのがエンコードです。ただし、URL全体とパラメータの値では扱いが違います。元に戻らないからと何度も変換する前に、どの部分を処理しているかを確認します。

URLエンコード・デコードツールでは、入力した文字列を変換できます。ログイン用トークンや個人情報を含まない短い例で動きを確かめてから、実際の処理へ当てはめる方が良いです。

URLの区切り記号と値を分ける

パーセントエンコードは、バイトを % と16進数2桁で表す仕組みです。UTF-8の「東京」は %E6%9D%B1%E4%BA%AC になります。暗号化ではないため、秘密情報を隠す用途には使えません。

? や & はURLの構造を区切る役割がありますが、検索語そのものに含めたいこともあります。たとえば検索語が「A&B」なら、値の中の & をそのまま入れると別のパラメータとの区切りとして解釈されます。URLに記号を使えないのではなく、置く場所によって変換が必要になります。

検索語: A&B
値をエンコード: A%26B
URL: https://example.com/search?q=A%26B

このURL全体をデコードして q=A&B に書き換え、再びアクセス用に使うと意味が変わります。人が読むための変換結果と、そのまま再利用してよいURLは分けてください。

JavaScriptでは値を一度だけ変換する

値だけを変換するなら encodeURIComponent を使えます。URLを組み立てる場合は、URL と searchParams に未変換の値を渡す方法もあります。

const encoded = encodeURIComponent("東京 A&B");
// %E6%9D%B1%E4%BA%AC%20A%26B

const url = new URL("https://example.com/search");
url.searchParams.set("q", "東京 A&B");
console.log(url.searchParams.get("q"));
// 東京 A&B

searchParams.set へ渡す前に、同じ値を encodeURIComponent で変換する必要はありません。get で取り出した値にも、通常は追加のデコードをしません。URLSearchParamsの処理はWHATWGのURL標準で定義されています。

encodeURI はURLの区切りを残すため、任意のパラメータ値を安全に組み立てる用途には向きません。値に含まれる記号も区切りとして残ることがあります。MDNのencodeURIComponentの説明にある違いを確認し、URL全体へ後から変換をかけるより、部品を分けて組み立てます。

プラス記号が空白になる場合とならない場合

+ は、フォーム形式のクエリを解析するときには空白として扱われます。一方、パス中の + は空白の代用ではありません。プラス記号として使える場所もあるので、一律に空白へ置換すると別のURLになります。

入力decodeURIComponentの結果URLSearchParamsでqの値として読んだ結果
A+BA+BA B
A%20BA BA B
A%2BBA+BA+B

たとえばフォームから「A+B」という値を送りたいなら、プラス自体を %2B として渡します。URL標準のフォーム解析規則では、プラスを空白へ置換した後にパーセントデコードします。「デコードしたら記号が消えた」場合は、この処理方式の違いを確認してください。

miniToolsのデコード処理は decodeURIComponent です。そのため、入力された + を自動的に空白へは変えません。フォーム形式を調べているのか、一般のパーセントエンコード文字列を調べているのかを区別して結果を読みます。

文字化けやURIErrorは元データまで戻って確認する

decodeURIComponent は、不完全な % 表記や正しいUTF-8にならないバイト列で URIError を返します。MDNの仕様説明に、この二つの条件が示されています。

症状確認する箇所最初の対応
%E3%81のように途中で終わるコピー・ログ保存時の欠落元の文字列を取り直す
%ZZや単独の%がある不正なエスケープか、未変換の文字か作成元で値を正しくエンコードする
古い日本語URLだけ失敗するShift_JISなどUTF-8以外で作られた可能性作成元の文字コードを確認する
変換は終わるが文字が崩れる変換前から壊れていないか元データと処理途中を比較する

例外を無視したり、読めない部分を削ったりしても、元の値を復元したことにはなりません。文字コードが分かった場合は、それに対応した処理でバイト列を文字へ戻します。すでに � などへ置き換わって保存されていれば、その文字列だけでは元に戻せない場合があります。

新しく実装する処理では、入力から保存までの文字コードを揃えます。既存データを一括変換する前には、原文を残したうえで一件だけ照合してください。

%25が残っても繰り返しデコードしない

%25 はパーセント記号自体のエンコードです。二重エンコードの手がかりになりますが、必ず誤りという意味ではありません。

元の値: 東京
1回エンコード: %E6%9D%B1%E4%BA%AC
2回エンコード: %25E6%259D%25B1%25E4%25BA%25AC

この例なら、一度戻すとエンコード済みの文字列が残ります。作成元で二回変換したと確認できれば、もう一段戻す理由があります。ただし、元の値が文字列としての %20 なら、その表現は %2520 で正しいものです。空白になるまで処理すると元の値を壊します。

入力時の値を残し、URLへ入れる直前の値と比べます。次に受信側で取り出した値を確認し、どこで自動変換されているかを調べます。重複した一段を取り除くことが修正であり、「読めるようになるまでデコード」は実装しない方が良いです。

PHPでも受信済みの値を重ねて戻さない

PHPの urldecode はプラスを空白へ変えますが、rawurldecode は変えません。フォーム形式かどうかで選ぶ点はJavaScriptと同じです。urldecodeとrawurldecodeの公式説明で確認できます。

echo urldecode('A+B');     // A B
echo rawurldecode('A+B');  // A+B

PHPの $_GET や $_REQUEST はすでにデコードされています。取り出した値へ無条件に urldecode を重ねると、本来のプラスやパーセント表記を変えてしまいます。

まずは元の文字列を残し、値を取り出す処理を確認してください。URLの転送中にパラメータ自体が消えているなら、文字変換とは別の問題です。HTTPステータス・リダイレクトチェッカーで移動先を調べ、消えた箇所を特定するところから進めます。