Analizuj ciąg identyfikatora URI w kolekcji nazwa-wartość

274

Mam taki identyfikator URI:

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

Potrzebuję kolekcji z parsowanymi elementami:

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

Aby być dokładnym, potrzebuję odpowiednika Java dla metody C # / .NET HttpUtility.ParseQueryString .

Proszę o radę w tej sprawie.

Dzięki.

Siergiej Szafiew
źródło
1
możliwy duplikat ciągów analizy składni w Javie
Matt Ball
@MattBall Jeśli OP używa Androida, to jest
Juan Mendes
156
Czy to nie zadziwiające, że nie jest to część podstawowego API java.net.URI/ java.net.URL?
Dilum Ranatunga
Sprawdź to rozwiązanie - solidna biblioteka i działający przykład dla operacji Parse i Format: stackoverflow.com/a/37744000/1882064
arcseldon

Odpowiedzi:

342

Jeśli szukasz sposobu na osiągnięcie tego celu bez korzystania z zewnętrznej biblioteki, pomoże ci następujący kod.

public static Map<String, String> splitQuery(URL url) throws UnsupportedEncodingException {
    Map<String, String> query_pairs = new LinkedHashMap<String, String>();
    String query = url.getQuery();
    String[] pairs = query.split("&");
    for (String pair : pairs) {
        int idx = pair.indexOf("=");
        query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
    }
    return query_pairs;
}

Możesz uzyskać dostęp do zwróconej mapy za <map>.get("client_id")pomocą adresu URL podanego w pytaniu, który zwróci „SS”.

Dodano UPDATE -dekodowanie adresu URL

AKTUALIZACJA Ponieważ ta odpowiedź jest nadal dość popularna, stworzyłem ulepszoną wersję powyższej metody, która obsługuje wiele parametrów z tym samym kluczem i parametry bez wartości.

public static Map<String, List<String>> splitQuery(URL url) throws UnsupportedEncodingException {
  final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
  final String[] pairs = url.getQuery().split("&");
  for (String pair : pairs) {
    final int idx = pair.indexOf("=");
    final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
    if (!query_pairs.containsKey(key)) {
      query_pairs.put(key, new LinkedList<String>());
    }
    final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
    query_pairs.get(key).add(value);
  }
  return query_pairs;
}

ZAKTUALIZUJ wersję Java8

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    return Arrays.stream(url.getQuery().split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, mapping(Map.Entry::getValue, toList())));
}

public SimpleImmutableEntry<String, String> splitQueryParameter(String it) {
    final int idx = it.indexOf("=");
    final String key = idx > 0 ? it.substring(0, idx) : it;
    final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null;
    return new SimpleImmutableEntry<>(key, value);
}

Uruchomienie powyższej metody z adresem URL

https://stackoverflow.com?param1=value1&param2=&param3=value3&param3

zwraca tę mapę:

{param1=["value1"], param2=[null], param3=["value3", null]}
Pr0gr4mm3r
źródło
13
Zapomniałeś zdekodować nazwy i parametry, jeden z powodów, dla których zwykle lepiej jest pozwolić bibliotekom wykonywać typowe zadania.
Juan Mendes
10
rzeczywiście masz rację ... ale ja osobiście wolę pisać takie „łatwe” zadania samodzielnie, niż używać własnej biblioteki do każdego zadania, które muszę wykonać.
Pr0gr4mm3r
2
jeśli masz wiele parametrów o tej samej nazwie / kluczu, użycie tej funkcji zastąpi wartość, która ma podobny klucz.
snowball147
4
@Chris Mylisz ucieczkę xml / html z kodowaniem URL. Przykładowy adres URL powinien wyglądać następująco: a.com/q?1=a%26b&2=b%26c
sceaj
3
fajnie byłoby wskazać, które funkcje są używane: Collectors.mapping (...) i Collectors.toList (...)
Thomas Rebele
311

org.apache.http.client.utils.URLEncodedUtils

jest dobrze znaną biblioteką, która może to dla Ciebie zrobić

import org.apache.hc.client5.http.utils.URLEncodedUtils

String url = "http://www.example.com/something.html?one=1&two=2&three=3&three=3a";

List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), Charset.forName("UTF-8"));

for (NameValuePair param : params) {
  System.out.println(param.getName() + " : " + param.getValue());
}

Wyjścia

one : 1
two : 2
three : 3
three : 3a
Juan Mendes
źródło
Czy mogę otrzymać wartość według nazwy bez przekazywania wszystkich elementów? Mam na myśli coś takiego: System.out.print (params [„one”]);
Siergiej Szafiew
3
@SergeyShafiev Przekonanie, List<NameValuePair>że Map<String,String>Java nie ma dostępu do nawiasów klamrowych, jest trywialne. Wyglądałoby to tak: map.get("one")jeśli nie wiesz, jak to zrobić, powinno to być inne pytanie (ale najpierw spróbuj sam) . Wolimy ograniczyć pytania tutaj, w SO
Juan Mendes
6
Uważaj, jeśli jeśli masz dwa razy ten sam parametr w swoim adresie URL (tj.? A = 1 i a = 2) URLEncodedUtils zgłosi wyjątek IllegalArgumentException
Crystark
10
@Crystark Od httpclient 4.3.3 ciąg zapytania o zduplikowanych nazwach nie zgłasza żadnych wyjątków. Działa zgodnie z oczekiwaniami. System.out.println(URLEncodedUtils.parse(new URI("http://example.com/?foo=bar&foo=baz"), "UTF-8"));wypisze [foo = bar, foo = baz] .
Akihiro HARAI
4
W systemie Android 6 biblioteka klienta Apache HTTP została usunięta. Oznacza to, że URLEncodedUtils and NameValuePair` nie są już dostępne (chyba że dodasz zależność do starszej biblioteki Apache, jak opisano tutaj ).
Ted Hopp,
108

Jeśli używasz Spring Framework:

public static void main(String[] args) {
    String uri = "http://my.test.com/test?param1=ab&param2=cd&param2=ef";
    MultiValueMap<String, String> parameters =
            UriComponentsBuilder.fromUriString(uri).build().getQueryParams();
    List<String> param1 = parameters.get("param1");
    List<String> param2 = parameters.get("param2");
    System.out.println("param1: " + param1.get(0));
    System.out.println("param2: " + param2.get(0) + "," + param2.get(1));
}

Dostaniesz:

param1: ab
param2: cd,ef
xxg
źródło
1
dla adresów URLUriComponentsBuilder.fromHttpUrl(url)
Lu55
51

użyj Google Guava i zrób to w 2 liniach:

import java.util.Map;
import com.google.common.base.Splitter;

public class Parser {
    public static void main(String... args) {
        String uri = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
        String query = uri.split("\\?")[1];
        final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(query);
        System.out.println(map);
    }
}

co daje ci

{client_id=SS, response_type=code, scope=N_FULL, access_type=offline, redirect_uri=http://localhost/Callback}
Amin Abbaspour
źródło
18
Co z dekodowaniem adresu URL opisanym w wybranej odpowiedzi?
Clint Eastwood
7
Jest to również podejrzane o wiele kluczy o tej samej nazwie. Według javadocs spowoduje to zgłoszenie
wyjątku
5
Zamiast ręcznego dzielenia uri, powinieneś użyć, new java.net.URL(uri).getQuery()ponieważ to kupuje bezpłatną weryfikację wejścia na adres URL.
avgvstvs,
1
Do dekodowania: końcowa mapa <ciąg, ciąg> zapytanieVars = Maps.transformValues ​​(mapa, nowa funkcja <ciąg, ciąg> () {@Override public String apply (wartość ciągu) {try {return URLDecoder.decode (wartość, "UTF- 8 ");} catch (UnsupportedEncodingException e) {// TODO Automatycznie wygenerowany blok catch e.printStackTrace ();} wartość zwracana;}});
phreakhead
11
OSTRZEŻENIE!! Nie jest to bezpieczne, ponieważ splitter.split()wyrzuci je, IllegalArgumentExceptionjeśli w ciągu zapytania znajduje się duplikat klucza. Zobacz stackoverflow.com/questions/1746507/…
Anderson
31

Najkrótszy sposób, jaki znalazłem, to ten:

MultiValueMap<String, String> queryParams =
            UriComponentsBuilder.fromUriString(url).build().getQueryParams();

AKTUALIZACJA: UriComponentsBuilder pochodzi z wiosny. Tutaj link .

Dmitrij Senkovich
źródło
3
Bez wiedzy, skąd pochodzi ta klasa UriComponentsBuilder, nie jest to bardzo przydatne.
Thomas Mortagne,
3
Warto zauważyć, że jest to dobry pomysł, jeśli korzystasz już z Springa. Jeśli nie korzystasz z Spring, będziesz chciał tego uniknąć. samatkinson.com/why-i-hate-spring
Nick
1
NB To wymaga identyfikatorów URI. Wersja identyfikatorów URI w języku Java nie jest nadzbiorem adresów URL (dlatego toURI może zgłaszać wyjątki).
Adam Gent
18

Dla Androida, jeśli używasz OkHttp w swoim projekcie. Możesz to zobaczyć. To proste i pomocne.

final HttpUrl url = HttpUrl.parse(query);
if (url != null) {
    final String target = url.queryParameter("target");
    final String id = url.queryParameter("id");
}
Niż Phearum
źródło
HttpUrl to dziwne imię, ale właśnie tego potrzebowałem. Dzięki.
GuiSim
10

Instrukcja Java 8 one

Biorąc pod uwagę adres URL do analizy:

URL url = new URL("https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback");

To rozwiązanie zbiera listę par:

List<AbstractMap.SimpleEntry<String, String>> list = 
        Pattern.compile("&").splitAsStream(url.getQuery())
        .map(s -> Arrays.copyOf(s.split("="), 2))
        .map(o -> new AbstractMap.SimpleEntry<String, String>(decode(o[0]), decode(o[1])))
        .collect(toList());

To rozwiązanie natomiast zbiera mapę (biorąc pod uwagę, że w adresie URL może znajdować się więcej parametrów o tej samej nazwie, ale o różnych wartościach).

Map<String, List<String>> list = 
        Pattern.compile("&").splitAsStream(url.getQuery())
        .map(s -> Arrays.copyOf(s.split("="), 2))
        .collect(groupingBy(s -> decode(s[0]), mapping(s -> decode(s[1]), toList())));

Oba rozwiązania muszą korzystać z funkcji narzędziowej, aby poprawnie dekodować parametry.

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);
    }
}
freedev
źródło
4
To bardziej podejście Java 8 niż oneliner Java 8.
Stephan
@ Stephan dobrze :) może oba. Ale bardziej interesuje mnie, jeśli podoba ci się to rozwiązanie.
freedev
3
IMO, oneliner powinien być krótki i nie powinien rozciągać się na wiele linii.
Stephan
1
W grę wchodzi wiele stwierdzeń.
Stephan
2
Wydaje mi się, że można napisać całą klasę w jednym wierszu, ale nie to zwykle oznacza wyrażenie „one-liner”.
Abhijit Sarkar,
10

Jeśli używasz serwletu, spróbuj, spróbuj

request.getParameterMap()

Zwraca java.util.Map parametrów tego żądania.

Zwraca: niezmienna java.util.Map zawierająca nazwy parametrów jako klucze i wartości parametrów jako wartości map. Klucze w mapie parametrów są typu String. Wartości w mapie parametrów są typu Tablica ciągów.

( Dokument Java )

Ansar Ozden
źródło
Działa to z Spring Web, a także w twoim kontrolerze możesz mieć parametr typu HttpServletRequesti działa MockHttpServletRequestrównież z testami jednostkowymi Mock MVC.
GameSalutes
8

Jeśli używasz Java 8 i chcesz napisać kilka metod wielokrotnego użytku, możesz to zrobić w jednym wierszu.

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);
    }
}

Ale to dość brutalna linia.

Fuwjax
źródło
3

Na Androida jest klasa Uri w pakiecie android.net . Pamiętaj, że Uri jest częścią android.net , podczas gdy URI jest częścią java.net .

Klasa Uri ma wiele funkcji do wydobywania par klucz-wartość z zapytania. wprowadź opis zdjęcia tutaj

Następująca funkcja zwraca pary klucz-wartość w postaci HashMap.

W Javie:

Map<String, String> getQueryKeyValueMap(Uri uri){
    HashMap<String, String> keyValueMap = new HashMap();
    String key;
    String value;

    Set<String> keyNamesList = uri.getQueryParameterNames();
    Iterator iterator = keyNamesList.iterator();

    while (iterator.hasNext()){
        key = (String) iterator.next();
        value = uri.getQueryParameter(key);
        keyValueMap.put(key, value);
    }
    return keyValueMap;
}

W Kotlinie:

fun getQueryKeyValueMap(uri: Uri): HashMap<String, String> {
        val keyValueMap = HashMap<String, String>()
        var key: String
        var value: String

        val keyNamesList = uri.queryParameterNames
        val iterator = keyNamesList.iterator()

        while (iterator.hasNext()) {
            key = iterator.next() as String
            value = uri.getQueryParameter(key) as String
            keyValueMap.put(key, value)
        }
        return keyValueMap
    }
Ramakrishna Joshi
źródło
2

Korzystając z wyżej wymienionych komentarzy i rozwiązań, przechowuję wszystkie parametry zapytania za pomocą Map <String, Object>, gdzie Objects może być ciągiem znaków lub Set <String>. Rozwiązanie podano poniżej. Zaleca się użycie pewnego rodzaju walidatora adresu URL w celu sprawdzenia poprawności adresu URL, a następnie wywołania metody convertQueryStringToMap.

private static final String DEFAULT_ENCODING_SCHEME = "UTF-8";

public static Map<String, Object> convertQueryStringToMap(String url) throws UnsupportedEncodingException, URISyntaxException {
    List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), DEFAULT_ENCODING_SCHEME);
    Map<String, Object> queryStringMap = new HashMap<>();
    for(NameValuePair param : params){
        queryStringMap.put(param.getName(), handleMultiValuedQueryParam(queryStringMap, param.getName(), param.getValue()));
    }
    return queryStringMap;
}

private static Object handleMultiValuedQueryParam(Map responseMap, String key, String value) {
    if (!responseMap.containsKey(key)) {
        return value.contains(",") ? new HashSet<String>(Arrays.asList(value.split(","))) : value;
    } else {
        Set<String> queryValueSet = responseMap.get(key) instanceof Set ? (Set<String>) responseMap.get(key) : new HashSet<String>();
        if (value.contains(",")) {
            queryValueSet.addAll(Arrays.asList(value.split(",")));
        } else {
            queryValueSet.add(value);
        }
        return queryValueSet;
    }
}
SUMIT
źródło
2

Wypróbowałem wersję Kotlin, widząc, jak to jest najlepszy wynik w Google.

@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
}

I metody rozszerzenia

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

fun String.decodeToUTF8(): String { 
    URLDecoder.decode(this, "UTF-8")
}
Graham Smith
źródło
1
Równe uznanie dla wysiłku stackoverflow.com/users/1203812/matthew-herod 50/50, ale nie może być współautorem.
Graham Smith,
2

Gotowe do użycia rozwiązanie do dekodowania części zapytania URI (w tym dekodowania i wartości wielu parametrów)

Komentarze

Nie byłem zadowolony z kodu dostarczonego przez @ Pr0gr4mm3r w https://stackoverflow.com/a/13592567/1211082 . Rozwiązanie oparte na Streamie nie wykonuje URLDecoding, zmienna wersja niezgrabna.

W ten sposób opracowałem rozwiązanie, które

  • Może rozłożyć część zapytania URI na Map<String, List<Optional<String>>>
  • Może obsługiwać wiele wartości dla tej samej nazwy parametru
  • Może poprawnie reprezentować parametry bez wartości ( Optional.empty()zamiastnull )
  • Prawidłowo dekoduje nazwy i wartości parametrów za pomocąURLdecode
  • Opiera się na strumieniach Java 8
  • Jest bezpośrednio użyteczny (patrz kod w tym import poniżej)
  • Pozwala na poprawną obsługę błędów (tutaj poprzez przekształcenie sprawdzonego wyjątku UnsupportedEncodingExceptionw wyjątek czasu wykonywania, RuntimeUnsupportedEncodingExceptionktóry umożliwia współdziałanie ze strumieniem. (Zawijanie zwykłej funkcji w funkcje zgłaszające sprawdzone wyjątki jest uciążliwe. A Scala Trynie jest dostępna w domyślnym języku Java).

Kod Java

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;
        }
    }
}

Kod Scala

... i ze względu na kompletność nie mogę się oprzeć dostarczeniu rozwiązania w Scali, które dominuje zwięzłość i piękno

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))
  }
}
Martin Senne
źródło
1

Tylko aktualizacja wersji Java 8

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    return Arrays.stream(url.getQuery().split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, **Collectors**.mapping(Map.Entry::getValue, **Collectors**.toList())));
}

Metody mapowania i toList () muszą być używane z modułami zbierającymi, których nie wymieniono w górnej odpowiedzi. W przeciwnym razie spowodowałoby to błąd kompilacji w IDE

Aarish Ramesh
źródło
wygląda na to, że musisz również udostępnić swoją splitQueryParameters()metodę? A co z tym **Collectors**?
Kirby
1

Odpowiedź Kotlina z początkowym odniesieniem od https://stackoverflow.com/a/51024552/3286489 , ale z ulepszoną wersją poprzez uporządkowanie kodów i udostępnienie 2 wersji oraz użycie niezmiennych operacji zbierania

Użyj, java.net.URIaby wyodrębnić zapytanie. Następnie użyj poniższych funkcji rozszerzenia

  1. Zakładając, że chcesz tylko ostatnią wartość zapytania, tzn. page2&page3Otrzymasz {page=3}, użyj poniższej funkcji rozszerzenia
    fun URI.getQueryMap(): Map<String, String> {
        if (query == null) return emptyMap()

        return query.split("&")
                .mapNotNull { element -> element.split("=")
                        .takeIf { it.size == 2 && it.none { it.isBlank() } } }
                .associateBy({ it[0].decodeUTF8() }, { it[1].decodeUTF8() })
    }

    private fun String.decodeUTF8() = URLDecoder.decode(this, "UTF-8") // decode page=%22ABC%22 to page="ABC"
  1. Zakładając, że chcesz listę wszystkich wartości dla zapytania, to znaczy page2&page3otrzymasz{page=[2, 3]}
    fun URI.getQueryMapList(): Map<String, List<String>> {
        if (query == null) return emptyMap()

        return query.split("&")
                .distinct()
                .mapNotNull { element -> element.split("=")
                        .takeIf { it.size == 2 && it.none { it.isBlank() } } }
                .groupBy({ it[0].decodeUTF8() }, { it[1].decodeUTF8() })
    }

    private fun String.decodeUTF8() = URLDecoder.decode(this, "UTF-8") // decode page=%22ABC%22 to page="ABC"

Sposób użycia go jak poniżej

    val uri = URI("schema://host/path/?page=&page=2&page=2&page=3")
    println(uri.getQueryMapList()) // Result is {page=[2, 3]}
    println(uri.getQueryMap()) // Result is {page=3}
Elye
źródło
1

Netty zapewnia również ładny parser ciągów zapytań o nazwie QueryStringDecoder. W jednym wierszu kodu może przeanalizować adres URL w pytaniu. Lubię, ponieważ nie wymaga łapania ani rzucania java.net.MalformedURLException.

W jednej linii:

Map<String, List<String>> parameters = new QueryStringDecoder(url).parameters();

Zobacz javadocs tutaj: https://netty.io/4.1/api/io/netty/handler/codec/http/QueryStringDecoder.html

Oto krótki, samodzielny, poprawny przykład:

import io.netty.handler.codec.http.QueryStringDecoder;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Map;

public class UrlParse {

  public static void main(String... args) {
    String url = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
    QueryStringDecoder decoder = new QueryStringDecoder(url);
    Map<String, List<String>> parameters = decoder.parameters();
    print(parameters);
  }

  private static void print(final Map<String, List<String>> parameters) {
    System.out.println("NAME               VALUE");
    System.out.println("------------------------");
    parameters.forEach((key, values) ->
        values.forEach(val ->
            System.out.println(StringUtils.rightPad(key, 19) + val)));
  }
}

który generuje

NAME               VALUE
------------------------
client_id          SS
response_type      code
scope              N_FULL
access_type        offline
redirect_uri       http://localhost/Callback
Kirby
źródło
0

Odpowiadając tutaj, ponieważ jest to popularny wątek. To czyste rozwiązanie w Kotlinie, które korzysta z zalecanego UrlQuerySanitizerinterfejsu API. Zobacz oficjalną dokumentację . Dodałem konstruktor ciągów, aby połączyć i wyświetlić parametry.

    var myURL: String? = null

    if (intent.hasExtra("my_value")) {
        myURL = intent.extras.getString("my_value")
    } else {
        myURL = intent.dataString
    }

    val sanitizer = UrlQuerySanitizer(myURL)
    // We don't want to manually define every expected query *key*, so we set this to true
    sanitizer.allowUnregisteredParamaters = true
    val parameterNamesToValues: List<UrlQuerySanitizer.ParameterValuePair> = sanitizer.parameterList
    val parameterIterator: Iterator<UrlQuerySanitizer.ParameterValuePair> = parameterNamesToValues.iterator()

    // Helper simply so we can display all values on screen
    val stringBuilder = StringBuilder()

    while (parameterIterator.hasNext()) {
        val parameterValuePair: UrlQuerySanitizer.ParameterValuePair = parameterIterator.next()
        val parameterName: String = parameterValuePair.mParameter
        val parameterValue: String = parameterValuePair.mValue

        // Append string to display all key value pairs
        stringBuilder.append("Key: $parameterName\nValue: $parameterValue\n\n")
    }

    // Set a textView's text to display the string
    val paramListString = stringBuilder.toString()
    val textView: TextView = findViewById(R.id.activity_title) as TextView
    textView.text = "Paramlist is \n\n$paramListString"

    // to check if the url has specific keys
    if (sanitizer.hasParameter("type")) {
        val type = sanitizer.getValue("type")
        println("sanitizer has type param $type")
    }
jungledev
źródło
0

Oto moje rozwiązanie z redukcją i opcjonalne :

private Optional<SimpleImmutableEntry<String, String>> splitKeyValue(String text) {
    String[] v = text.split("=");
    if (v.length == 1 || v.length == 2) {
        String key = URLDecoder.decode(v[0], StandardCharsets.UTF_8);
        String value = v.length == 2 ? URLDecoder.decode(v[1], StandardCharsets.UTF_8) : null;
        return Optional.of(new SimpleImmutableEntry<String, String>(key, value));
    } else
        return Optional.empty();
}

private HashMap<String, String> parseQuery(URI uri) {
    HashMap<String, String> params = Arrays.stream(uri.getQuery()
            .split("&"))
            .map(this::splitKeyValue)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .reduce(
                // initial value
                new HashMap<String, String>(), 
                // accumulator
                (map, kv) -> {
                     map.put(kv.getKey(), kv.getValue()); 
                     return map;
                }, 
                // combiner
                (a, b) -> {
                     a.putAll(b); 
                     return a;
                });
    return params;
}
  • Ignoruję zduplikowane parametry (biorę ostatni).
  • Używam Optional<SimpleImmutableEntry<String, String>>ignorować śmieci później
  • Redukcja zaczyna się od pustej mapy, a następnie wypełnia ją na każdym SimpleImmutableEntry

Jeśli zapytasz, redukcja wymaga tego dziwnego sumatora w ostatnim parametrze, który jest używany tylko w równoległych strumieniach. Jego celem jest połączenie dwóch wyników pośrednich (tutaj HashMap).

Mike Dudley
źródło
-1

Jeśli używasz Springa, dodaj argument typu @RequestParam Map<String,String> do metody kontrolera, a Spring zbuduje dla Ciebie mapę!

mancini0
źródło