372 Stimmen

Parsen einer URI-Zeichenkette in eine Name-Werte-Sammlung

Ich habe die URI so eingestellt:

https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback

Ich brauche eine Sammlung mit geparsten Elementen:

NAME               VALUE
------------------------
client_id          SS
response_type      code
scope              N_FULL
access_type        offline
redirect_uri       http://localhost/Callback

Um genau zu sein, brauche ich ein Java-Äquivalent für die C#/.NET HttpUtility.ParseQueryString Methode.

8voto

Fuwjax Punkte 2197

Wenn Sie Java 8 verwenden und bereit sind, ein paar wiederverwendbare Methoden zu schreiben, können Sie dies in einer Zeile tun.

private Map<String, List<String>> parse(final String query) {
    return Arrays.asList(query.split("&")).stream().map(p -> p.split("=")).collect(Collectors.toMap(s -> decode(index(s, 0)), s -> Arrays.asList(decode(index(s, 1))), this::mergeLists));
}

private <T> List<T> mergeLists(final List<T> l1, final List<T> l2) {
    List<T> list = new ArrayList<>();
    list.addAll(l1);
    list.addAll(l2);
    return list;
}

private static <T> T index(final T[] array, final int index) {
    return index >= array.length ? null : array[index];
}

private static String decode(final String encoded) {
    try {
        return encoded == null ? null : URLDecoder.decode(encoded, "UTF-8");
    } catch(final UnsupportedEncodingException e) {
        throw new RuntimeException("Impossible: UTF-8 is a required encoding", e);
    }
}

Aber das ist eine ziemlich brutale Linie.

5voto

Enigo Punkte 3414

Es gibt eine neue Version des Apache HTTP-Client - org.apache.httpcomponents.client5 - wo URLEncodedUtils ist jetzt veraltet. URIBuilder sollte stattdessen verwendet werden:

import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.net.URIBuilder;

private static Map<String, String> getQueryParameters(final String url) throws URISyntaxException {
    return new URIBuilder(new URI(url), StandardCharsets.UTF_8).getQueryParams()
                                                               .stream()
                                                               .collect(Collectors.toMap(NameValuePair::getName,
                                                                                         nameValuePair -> URLDecoder.decode(nameValuePair.getValue(), StandardCharsets.UTF_8)));
}

3voto

Martin Senne Punkte 5731

Eine einsatzbereite Lösung für die Dekodierung des URI-Abfrageteils (einschließlich Dekodierung und Multiparameterwerte)

Kommentare

Ich war nicht zufrieden mit dem von @Pr0gr4mm3r bereitgestellten Code in https://stackoverflow.com/a/13592567/1211082 . Die Stream-basierte Lösung führt kein URLDecoding durch, die veränderbare Version ist klumpig.

Daher habe ich eine Lösung erarbeitet, die

  • Kann einen URI-Abfrageteil in einen Map<String, List<Optional<String>>>
  • Dose mehrere Werte verarbeiten für denselben Parameternamen
  • Dose Parameter ohne Wert richtig darstellen ( Optional.empty() anstelle von null )
  • Dekodiert Parameternamen y Werte korrekt über URLdecode
  • Basiert auf Java 8 Streams
  • Ist direkt verwendbar (siehe Code einschließlich Importe unten)
  • Ermöglicht eine ordnungsgemäße Fehlerbehandlung (hier durch Umwandlung einer geprüften Ausnahme UnsupportedEncodingException in eine Laufzeitausnahme RuntimeUnsupportedEncodingException die das Zusammenspiel mit Stream ermöglicht. (Reguläre Funktionen in Funktionen zu verpacken, die geprüfte Ausnahmen auslösen, ist eine Qual. Und Scala Try ist in der Java-Sprachvoreinstellung nicht verfügbar).

Java-Code

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import static java.util.stream.Collectors.*;

public class URIParameterDecode {
    /**
     * Decode parameters in query part of a URI into a map from parameter name to its parameter values.
     * For parameters that occur multiple times each value is collected.
     * Proper decoding of the parameters is performed.
     * 
     * Example
     *   <pre>a=1&b=2&c=&a=4</pre>
     * is converted into
     *   <pre>{a=[Optional[1], Optional[4]], b=[Optional[2]], c=[Optional.empty]}</pre>
     * @param query the query part of an URI 
     * @return map of parameters names into a list of their values.
     *         
     */
    public static Map<String, List<Optional<String>>> splitQuery(String query) {
        if (query == null || query.isEmpty()) {
            return Collections.emptyMap();
        }

        return Arrays.stream(query.split("&"))
                    .map(p -> splitQueryParameter(p))
                    .collect(groupingBy(e -> e.get0(), // group by parameter name
                            mapping(e -> e.get1(), toList())));// keep parameter values and assemble into list
    }

    public static Pair<String, Optional<String>> splitQueryParameter(String parameter) {
        final String enc = "UTF-8";
        List<String> keyValue = Arrays.stream(parameter.split("="))
                .map(e -> {
                    try {
                        return URLDecoder.decode(e, enc);
                    } catch (UnsupportedEncodingException ex) {
                        throw new RuntimeUnsupportedEncodingException(ex);
                    }
                }).collect(toList());

        if (keyValue.size() == 2) {
            return new Pair(keyValue.get(0), Optional.of(keyValue.get(1)));
        } else {
            return new Pair(keyValue.get(0), Optional.empty());
        }
    }

    /** Runtime exception (instead of checked exception) to denote unsupported enconding */
    public static class RuntimeUnsupportedEncodingException extends RuntimeException {
        public RuntimeUnsupportedEncodingException(Throwable cause) {
            super(cause);
        }
    }

    /**
     * A simple pair of two elements
     * @param <U> first element
     * @param <V> second element
     */
    public static class Pair<U, V> {
        U a;
        V b;

        public Pair(U u, V v) {
            this.a = u;
            this.b = v;
        }

        public U get0() {
            return a;
        }

        public V get1() {
            return b;
        }
    }
}

Scala-Code

... und der Vollständigkeit halber kann ich nicht widerstehen, die Lösung in Scala anzubieten, die durch Kürze und Schönheit besticht

import java.net.URLDecoder

object Decode {
  def main(args: Array[String]): Unit = {
    val input = "a=1&b=2&c=&a=4";
    println(separate(input))
  }

  def separate(input: String) : Map[String, List[Option[String]]] = {
    case class Parameter(key: String, value: Option[String])

    def separateParameter(parameter: String) : Parameter =
      parameter.split("=")
               .map(e => URLDecoder.decode(e, "UTF-8")) match {
      case Array(key, value) =>  Parameter(key, Some(value))
      case Array(key) => Parameter(key, None)
    }

    input.split("&").toList
      .map(p => separateParameter(p))
      .groupBy(p => p.key)
      .mapValues(vs => vs.map(p => p.value))
  }
}

2voto

Außerdem würde ich eine Regex-basierte Implementierung von URLParser

import java.util.regex.Matcher;
import java.util.regex.Pattern;

class URLParser {
    private final String query;

    public URLParser(String query) {
        this.query = query;
    }

    public String get(String name) {
        String regex = "(?:^|\\?|&)" + name + "=(.*?)(?:&|$)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(this.query);

        if (matcher.find()) {
            return matcher.group(1);
        }

        return "";
    }
}

Diese Klasse ist einfach zu benutzen. Sie benötigt nur die URL oder den Query-String bei der Initialisierung und parst den Wert nach dem angegebenen Schlüssel.

class Main {
    public static void main(String[] args) {
        URLParser parser = new URLParser("https://www.google.com/search?q=java+parse+url+params&oq=java+parse+url+params&aqs=chrome..69i57j0i10.18908j0j7&sourceid=chrome&ie=UTF-8");
        System.out.println(parser.get("q"));  // java+parse+url+params
        System.out.println(parser.get("sourceid"));  // chrome
        System.out.println(parser.get("ie"));  // UTF-8
    }
}

2voto

Graham Smith Punkte 25377

Ich habe mich an einer Kotlin-Version versucht, da dies das Top-Ergebnis in Google ist.

@Throws(UnsupportedEncodingException::class)
fun splitQuery(url: URL): Map<String, List<String>> {

    val queryPairs = LinkedHashMap<String, ArrayList<String>>()

    url.query.split("&".toRegex())
            .dropLastWhile { it.isEmpty() }
            .map { it.split('=') }
            .map { it.getOrEmpty(0).decodeToUTF8() to it.getOrEmpty(1).decodeToUTF8() }
            .forEach { (key, value) ->

                if (!queryPairs.containsKey(key)) {
                    queryPairs[key] = arrayListOf(value)
                } else {

                    if(!queryPairs[key]!!.contains(value)) {
                        queryPairs[key]!!.add(value)
                    }
                }
            }

    return queryPairs
}

Und die Erweiterungsmethoden

fun List<String>.getOrEmpty(index: Int) : String {
    return getOrElse(index) {""}
}

fun String.decodeToUTF8(): String { 
    URLDecoder.decode(this, "UTF-8")
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X