Analizar una cadena URI en una colección de nombre y valor

274

Tengo el URI como este:

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

Necesito una colección con elementos analizados:

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

Para ser exactos, necesito un equivalente de Java para el método C # / .NET HttpUtility.ParseQueryString .

Por favor, dame un consejo sobre esto.

Gracias.

Sergey Shafiev
fuente
1
posible duplicado de cadenas de consulta
Matt Ball
@MattBall Si el OP está usando Android, entonces lo es
Juan Mendes
156
¿No es alucinante que esto no sea parte de la API principal de java.net.URI/ java.net.URL?
Dilum Ranatunga
Compruebe esta solución: biblioteca sólida y ejemplo de trabajo para las operaciones de análisis y formato: stackoverflow.com/a/37744000/1882064
arcseldon

Respuestas:

342

Si está buscando una manera de lograrlo sin usar una biblioteca externa, el siguiente código lo ayudará.

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

Puede acceder al Mapa devuelto utilizando <map>.get("client_id"), con la URL dada en su pregunta, esto devolvería "SS".

ACTUALIZAR URL-Decodificación agregada

ACTUALIZACIÓN Como esta respuesta sigue siendo bastante popular, hice una versión mejorada del método anterior, que maneja múltiples parámetros con la misma clave y parámetros sin valor también.

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

ACTUALIZAR versión 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);
}

Ejecutando el método anterior con la URL

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

devuelve este mapa:

{param1=["value1"], param2=[null], param3=["value3", null]}
Pr0gr4mm3r
fuente
13
Olvida decodificar los nombres y parámetros, una de las razones por las que generalmente es mejor dejar que las bibliotecas realicen tareas comunes.
Juan Mendes
10
de hecho, tienes razón ... pero personalmente prefiero escribir tareas "fáciles" por mí mismo, en lugar de usar una biblioteca propia para cada tarea que tengo que hacer.
Pr0gr4mm3r
2
Si tiene varios parámetros con el mismo nombre / clave, el uso de esta función anulará el valor que tenga una clave similar.
snowball147
44
@ Chris Estás confundiendo el escape de xml / html con la codificación de URL. Su URL de ejemplo debe ser: a.com/q?1=a%26b&2=b%26c
sceaj
3
Sería bueno indicar qué funciones se utilizan: Collectors.mapping (...) y Collectors.toList (...)
Thomas Rebele
311

org.apache.http.client.utils.URLEncodedUtils

es una biblioteca bien conocida que puede hacerlo por ti

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

Salidas

one : 1
two : 2
three : 3
three : 3a
Juan mendes
fuente
¿Puedo recibir el valor por su nombre sin pasar todos los elementos? Me refiero a algo como esto: System.out.print (params ["one"]);
Sergey Shafiev
3
@SergeyShafiev Es trivial para convertir una List<NameValuePair>en una Map<String,String>de Java no tiene acceso a soporte para mapas de hash, se vería como map.get("one")si usted no sabe cómo hacerlo, debe ser otra pregunta (pero probarlo en su propia primera) . Preferimos mantener las preguntas escasas aquí en SO
Juan Mendes
66
Tenga cuidado de que si tiene dos veces el mismo parámetro en su URL (es decir? A = 1 & a = 2) URLEncodedUtils arrojará una IllegalArgumentException
Crystark
10
@Crystark A partir de httpclient 4.3.3, la cadena de consulta con nombres duplicados no arroja ninguna excepción. Funciona como se esperaba. System.out.println(URLEncodedUtils.parse(new URI("http://example.com/?foo=bar&foo=baz"), "UTF-8"));imprimirá [foo = bar, foo = baz] .
Akihiro HARAI
44
A partir de Android 6, la biblioteca del cliente Apache HTTP se ha eliminado. Esto significa que URLEncodedUtils and NameValuePair` ya no están disponibles (a menos que agregue una dependencia a la biblioteca Apache heredada como se describe aquí ).
Ted Hopp
109

Si está utilizando 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));
}

Conseguirás:

param1: ab
param2: cd,ef
xxg
fuente
1
para uso de URLUriComponentsBuilder.fromHttpUrl(url)
Lu55
51

usa google guava y hazlo en 2 líneas:

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

que te da

{client_id=SS, response_type=code, scope=N_FULL, access_type=offline, redirect_uri=http://localhost/Callback}
Amin Abbaspour
fuente
18
¿Qué pasa con la decodificación de URL descrita en la respuesta seleccionada?
Clint Eastwood
77
Esto también es sospechoso para múltiples claves con el mismo nombre. Según los javadocs, esto arrojará una IllegalArgumentException
jontro
55
En lugar de dividirlo manualmente uri, debe usarlo, new java.net.URL(uri).getQuery()ya que esto le da una validación de entrada gratuita en la URL.
avgvstvs
1
Para la decodificación: Mapa final <String, String> queryVars = Maps.transformValues ​​(map, new Function <String, String> () {@Override public String apply (String value) {try {return URLDecoder.decode (value, "UTF- 8 ");} catch (UnsupportedEncodingException e) {// TODO Bloque de captura generado automáticamente e.printStackTrace ();} valor de retorno;}});
phreakhead
11
¡¡ADVERTENCIA!! NO es seguro hacer esto, ya splitter.split()que arrojará IllegalArgumentExceptionsi hay una clave duplicada en la cadena de consulta. Ver stackoverflow.com/questions/1746507/…
Anderson
31

La forma más corta que he encontrado es esta:

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

ACTUALIZACIÓN: UriComponentsBuilder proviene de la primavera. Aquí el enlace .

Dmitry Senkovich
fuente
3
Sin saber de dónde proviene esta clase UriComponentsBuilder, no es muy útil.
Thomas Mortagne
3
Vale la pena señalar que esta es solo una buena idea si ya está utilizando Spring. Si no está utilizando Spring, querrá evitarlo. samatkinson.com/why-i-hate-spring
Nick
1
NB Esto toma URIs. La versión de Java de los URI no es un superconjunto de URL (es por eso que toURI puede lanzar excepciones).
Adam Gent
18

Para Android, si está utilizando OkHttp en su proyecto. Puede echar un vistazo a esto. Es simple y útil.

final HttpUrl url = HttpUrl.parse(query);
if (url != null) {
    final String target = url.queryParameter("target");
    final String id = url.queryParameter("id");
}
THANN Phearum
fuente
HttpUrl es una especie de nombre extraño, pero esto es exactamente lo que necesitaba. Gracias.
GuiSim
10

Declaración de Java 8 one

Dada la URL para analizar:

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

Esta solución recopila una lista de pares:

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

Esta solución, por otro lado, recopila un mapa (dado que en una url puede haber más parámetros con el mismo nombre pero con valores diferentes).

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

Ambas soluciones deben usar una función de utilidad para decodificar adecuadamente los parámetros.

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
fuente
44
Este es más un enfoque de Java 8 que un oneliner de Java 8.
Stephan
@Stephan bueno :) tal vez ambos. Pero estoy más interesado en entender si te gusta esta solución.
freedev
3
En mi opinión, un oneliner debe ser corto y no debe abarcar varias líneas.
Stephan
1
Hay varias declaraciones involucradas aquí.
Stephan
2
Supongo que podrías escribir una clase completa en una sola línea, pero eso no es lo que generalmente significa la frase "one-liner".
Abhijit Sarkar
10

Si está utilizando servlet doGet intente esto

request.getParameterMap()

Devuelve un java.util.Map de los parámetros de esta solicitud.

Devuelve: un inmutable java.util.Map que contiene nombres de parámetros como claves y valores de parámetros como valores de mapa. Las claves en el mapa de parámetros son de tipo String. Los valores en el mapa de parámetros son de tipo String array.

( Documento de Java )

Ansar Ozden
fuente
Esto funciona con Spring Web, así como en su controlador puede tener un parámetro de tipo HttpServletRequesty también funciona MockHttpServletRequesten las pruebas unitarias Mock MVC.
GameSalutes
8

Si está utilizando Java 8 y está dispuesto a escribir algunos métodos reutilizables, puede hacerlo en una línea.

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

Pero esa es una línea bastante brutal.

Fuwjax
fuente
3

En Android, hay una clase Uri en el paquete android.net . Tenga en cuenta que Uri es parte de android.net , mientras que URI es parte de java.net .

La clase Uri tiene muchas funciones para extraer pares clave-valor de una consulta. ingrese la descripción de la imagen aquí

La siguiente función devuelve pares clave-valor en forma de HashMap.

En Java:

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

En Kotlin:

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
fuente
2

Utilizando los comentarios y soluciones mencionados anteriormente, estoy almacenando todos los parámetros de consulta usando Map <String, Object> donde los Objetos pueden ser string o Set <String>. La solución se da a continuación. Se recomienda utilizar algún tipo de validador de url para validar primero la url y luego llamar al método 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;
    }
}
SUMITAR
fuente
2

Probé una versión de Kotlin para ver cómo este es el mejor resultado en 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
}

Y los métodos de extensión

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

fun String.decodeToUTF8(): String { 
    URLDecoder.decode(this, "UTF-8")
}
Graham Smith
fuente
1
Igual crédito para stackoverflow.com/users/1203812/matthew-herod 50/50 esfuerzo pero no puede ser coautor.
Graham Smith
2

Una solución lista para usar para la decodificación de la parte de consulta URI (incluida la decodificación y los valores de parámetros múltiples)

Comentarios

No estaba contento con el código provisto por @ Pr0gr4mm3r en https://stackoverflow.com/a/13592567/1211082 . La solución basada en Stream no hace URLDecoding, la versión mutable agrupada.

Así elaboré una solución que

  • Puede descomponer una parte de consulta URI en un Map<String, List<Optional<String>>>
  • Puede manejar múltiples valores para el mismo nombre de parámetro
  • Puede representar parámetros sin un valor correctamente (en Optional.empty()lugar de null)
  • Decodifica nombres y valores de parámetros correctamente través deURLdecode
  • Está basado en Java 8 Streams
  • Es directamente utilizable (ver código incluyendo importaciones abajo)
  • Permite el manejo adecuado de errores (aquí mediante la conversión de una excepción marcada UnsupportedEncodingExceptionen una excepción de tiempo de ejecución RuntimeUnsupportedEncodingExceptionque permite la interacción con la transmisión. (Empaquetar la función regular en funciones que arrojan excepciones marcadas es una molestia. Y Scala Tryno está disponible en el idioma predeterminado de Java).

Código 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;
        }
    }
}

Código Scala

... y en aras de la exhaustividad no puedo resistirme a proporcionar la solución en Scala que domina por brevedad y belleza

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
fuente
1

Solo una actualización de la versión 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())));
}

los métodos de mapeo y toList () deben usarse con los recopiladores que no se mencionaron en la respuesta principal. De lo contrario, arrojaría un error de compilación en IDE

Aarish Ramesh
fuente
parece que también necesitas compartir tu splitQueryParameters()método? ¿Y qué pasa con el **Collectors**?
Kirby
1

Respuesta de Kotlin con referencia inicial de https://stackoverflow.com/a/51024552/3286489 , pero con una versión mejorada al ordenar los códigos y proporciona 2 versiones de la misma, y ​​usar operaciones de colección inmutables

Use java.net.URIpara extraer la consulta. Luego use las siguientes funciones de extensión proporcionadas

  1. Suponiendo que solo desea el último valor de consulta, es decir page2&page3, obtendrá {page=3}, use la función de extensión a continuación
    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. Suponiendo que desea una lista de todos los valores para la consulta, es decir page2&page3, obtendrá{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"

La forma de usarlo como a continuación

    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
fuente
1

Netty también proporciona un buen analizador de cadenas de consulta llamado QueryStringDecoder. En una línea de código, puede analizar la URL en la pregunta. Me gusta porque no requiere atrapar o tirarjava.net.MalformedURLException .

En una linea:

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

Ver javadocs aquí: https://netty.io/4.1/api/io/netty/handler/codec/http/QueryStringDecoder.html

Aquí hay un ejemplo corto, autónomo y correcto:

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

que genera

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

Respondiendo aquí porque este es un hilo popular. Esta es una solución limpia en Kotlin que utiliza la UrlQuerySanitizerAPI recomendada . Ver la documentación oficial . He agregado un generador de cadenas para concatenar y mostrar los parámetros.

    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
fuente
0

Aquí está mi solución con reduce y Opcional :

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;
}
  • Ignoro los parámetros duplicados (tomo el último).
  • Yo uso Optional<SimpleImmutableEntry<String, String>>de ignorar la basura más tarde
  • La reducción comienza con un mapa vacío, luego lo completa en cada SimpleImmutableEntry

En caso de que pregunte, reduce requiere este combinador extraño en el último parámetro, que solo se usa en flujos paralelos. Su objetivo es fusionar dos resultados intermedios (aquí HashMap).

Mike Dudley
fuente
-1

Si está utilizando Spring, agregue un argumento de tipo @RequestParam Map<String,String> a su método de controlador, ¡y Spring construirá el mapa por usted!

mancini0
fuente