Java Tips | 基本ユーティリティ:URLデコード

Java Java
スポンサーリンク

URLデコードは「URLの中身を人間とプログラムの世界に戻す」技

URLエンコードが「暴れそうな文字を URL の中で安全な形に変える」技だとしたら、
URLデコードはその逆で、「%E3%81%82 みたいな記号だらけの文字列を、本来の意味のある文字列に戻す」技です。

ここを雑にやると、「日本語が文字化けする」「+ がスペースになったりならなかったりする」「二重デコードで壊れる」といった、
地味だけど厄介なバグが出ます。だからこそ、“どこからでも同じルールで URL を戻せるユーティリティ” を一つ持っておくと、コードが安定します。


URLデコードのイメージをつかむ

「%XX や + を、元の文字に戻す」

URLエンコードでは、URLの中で問題を起こしそうな文字を %E3%81%82 のような形に変えました。
URLデコードは、その逆変換です。

例えば、次のようなクエリ文字列があったとします。

q=Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89

これを URLデコードすると、q の値はこうなります。

Java URL デコード

ここで大事なのは、「URLデコードは“どの場面でエンコードされたか”とセットで考える必要がある」ということです。
特に、+ をスペースとみなすかどうかは、「フォーム形式(application/x-www-form-urlencoded)かどうか」で変わります。
Java の URLDecoder はフォーム形式前提なので、+ をスペースとして扱います。


Java の URLDecoder をそのまま散らばらせない

URLDecoder の基本と「罠」

Java には java.net.URLDecoder というクラスがあります。
これを使えば簡単に URLデコードできます。

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

public class RawUrlDecodeExample {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String decoded = URLDecoder.decode("Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89", "UTF-8");
        System.out.println(decoded); // Java URL デコード
    }
}
Java

動きは分かりやすいのですが、そのまま業務コードに散らばらせると、次のような問題が出ます。

文字コードを文字列で指定するので、”UTF-8″ のタイプミスがコンパイル時に検出されない。
UTF-8 以外をうっかり使うと文字化けする。
UnsupportedEncodingException というチェック例外を毎回ハンドリングしないといけない。

さらに、URLDecoder は「フォーム形式(application/x-www-form-urlencoded)」前提なので、
+ をスペースとして扱います。これはクエリパラメータでは一般的に期待どおりですが、
「URL全体」や「パス部分」をデコードしたいときには、意図とズレることがあります。

だからこそ、用途を絞った小さなユーティリティで包んでしまうのが現実的です。


クエリパラメータ用の URLデコードユーティリティを作る

UTF-8 固定・フォーム形式前提の decodeQueryParam

まずは、「クエリパラメータの値をデコードする」ことに特化したユーティリティを作ります。

import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

public final class UrlDecoders {

    private UrlDecoders() {}

    public static String decodeQueryParam(String value) {
        if (value == null) {
            return null;
        }
        try {
            return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new UncheckedIOException(e);
        }
    }
}
Java

ここで深掘りしたい重要ポイントは三つです。

一つ目は、「UTF-8 を固定している」ことです。
エンコード側(URLEncoder.encode(..., "UTF-8")StandardCharsets.UTF_8)とペアにするためにも、
デコード側も必ず UTF-8 を明示しておきます。

二つ目は、「null をそのまま null として返す」ことです。
エンコード側で「null は空文字にする」と決めているなら、デコード側もそれに合わせる、というように、
プロジェクトとしての方針をここで決めておくと、呼び出し側が迷いません。

三つ目は、「UnsupportedEncodingException を UncheckedIOException にラップしている」ことです。
UTF-8 は Java 標準で必ずサポートされているので、本来この例外は起きません。
「起きたらプログラミングミス」とみなしてランタイム例外にしてしまう設計がよく使われます。


例題:クエリ文字列からパラメータを取り出してデコードする

name=...&age=... を Map に落とし込む

URLデコードが必要になる典型的な場面が、「クエリ文字列をパースして、パラメータを取り出す」処理です。
例えば、次のようなクエリ文字列があったとします。

name=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&age=30&keyword=Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89

これを Map<String, String> に落とし込むユーティリティを作ってみます。

import java.util.LinkedHashMap;
import java.util.Map;

public final class QueryStringParser {

    private QueryStringParser() {}

    public static Map<String, String> parse(String query) {
        Map<String, String> result = new LinkedHashMap<>();
        if (query == null || query.isEmpty()) {
            return result;
        }

        String[] pairs = query.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf('=');
            String rawName;
            String rawValue;
            if (idx >= 0) {
                rawName  = pair.substring(0, idx);
                rawValue = pair.substring(idx + 1);
            } else {
                rawName  = pair;
                rawValue = "";
            }

            String name  = UrlDecoders.decodeQueryParam(rawName);
            String value = UrlDecoders.decodeQueryParam(rawValue);
            result.put(name, value);
        }
        return result;
    }
}
Java

使い方はこうです。

String query = "name=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&age=30&keyword=Java+URL+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89";

Map<String, String> params = QueryStringParser.parse(query);

System.out.println(params.get("name"));    // 山田太郎
System.out.println(params.get("age"));     // 30
System.out.println(params.get("keyword")); // Java URL デコード
Java

ここでの重要ポイントは、「“分解(split)”と“デコード”をきちんと分けている」ことです。
&= でクエリ文字列を分解したあと、キーと値をそれぞれ decodeQueryParam に通しています。

これにより、「構造としてのクエリパラメータ」と「エンコードされた値」をきれいに分離して扱えるようになります。


例題:リダイレクトURLの中の「戻り先URL」を取り出す

URLの中に入っている URL をデコードする

URLエンコードのときにやった、「ログイン画面にリダイレクトするとき、戻り先URLをクエリパラメータに載せる」パターンを、今度は逆側から見てみます。

例えば、こんな URL が来たとします。

https://example.com/login?redirect=https%3A%2F%2Fexample.com%2Fapp%2Fhome%3Ftab%3Dprofile

この中から redirect の値を取り出し、元の URL に戻したいとします。

String url = "https://example.com/login?redirect=https%3A%2F%2Fexample.com%2Fapp%2Fhome%3Ftab%3Dprofile";

String query = url.substring(url.indexOf('?') + 1); // login? の後ろだけを取り出す
Map<String, String> params = QueryStringParser.parse(query);

String redirect = params.get("redirect");
System.out.println(redirect); // https://example.com/app/home?tab=profile
Java

ここで深掘りしたいのは、「“URL全体”をデコードしているのではなく、“クエリパラメータの値としての URL”をデコードしている」という点です。
外側の ?& は構造を表す記号なのでそのまま、
内側の URL は「値」として扱うので、:/?= を含めてエンコード・デコードしています。

この「どこまでが構造で、どこからが値か」を意識できるようになると、URLデコードの使いどころが一気にクリアになります。


URLデコードで気をつけたいポイント

エンコード時と「文字コード」と「方式」を合わせる

URLデコードで一番大事なのは、「エンコード時と同じ前提でデコードする」ことです。

エンコード側がこうだったなら、

String encoded = URLEncoder.encode(value, "UTF-8");
Java

デコード側は必ずこうでなければいけません。

String decoded = URLDecoder.decode(encoded, "UTF-8");
Java

ここで、片方だけ Shift_JIS にしたり、片方だけ URLDecoder を使わずに生の文字列として扱ったりすると、
文字化けやデコードエラーの原因になります。

また、「フォーム形式(+ をスペースとみなす)」前提でエンコードしているなら、
デコード側も同じ前提(URLDecoder)で戻す必要があります。

二重デコードをしない

よくある失敗が、「すでにデコード済みの文字列を、もう一度デコードしてしまう」パターンです。

例えば、%E3%81%82 を一度デコードして にしたあと、
さらに URLDecoder.decode("あ", "UTF-8") を呼んでも、今度は % も何もないので意味がありません。

逆に、「二重エンコード」された文字列(%25E3%2581%2582 のようなもの)を一回だけデコードすると、
%E3%81%82 になってしまい、さらにもう一回デコードしないと になりません。

「どのタイミングでエンコードしたか」「どのタイミングでデコードするか」を設計として決めておき、
「同じ値を複数回デコードしない」という意識を持つことが大事です。


まとめ:URLデコードユーティリティで身につけたい感覚

URLデコードは、「URLの中で安全な形にされていた値を、人間とプログラムが扱いやすい形に戻す」技です。

押さえておきたい感覚は、まず「URLデコードは“値”に対して行うものであり、URL全体を丸ごとデコードするものではない」こと。
次に、「URLDecoder をそのまま散らばらせず、UTF-8 固定・用途別(クエリ用)のユーティリティに閉じ込める」こと。
そして、「クエリ文字列の分解とデコードをユーティリティに任せることで、& や =、文字化け、二重デコードといった細かいミスを根こそぎ減らす」ことです。

タイトルとURLをコピーしました