Wie konvertiere ich eine Karte in eine URL-Abfragezeichenfolge?

77

Kennen Sie eine Dienstprogrammklasse / -bibliothek, die Map in eine URL-freundliche Abfragezeichenfolge konvertieren kann?

Beispiel:

Ich habe eine Karte:

"param1"=12,
"param2"="cat"

Ich möchte bekommen:

param1=12&param2=cat

endgültige Ausgabe

relativeUrl+param1=12&param2=cat
Ula Krukar
quelle
Gute Frage. Ein bisschen überraschend, wenn es dafür keinen Nutzen gibt ... Ich konnte keinen schnell finden.
Jonik

Antworten:

68

Die robusteste, die ich von der Stange gesehen habe, ist die URLEncodedUtils- Klasse von Apache Http Compoments (HttpClient 4.0).

Die Methode URLEncodedUtils.format()ist genau das, was Sie brauchen.

Es wird keine Karte verwendet, sodass Sie doppelte Parameternamen haben können, wie z.

  a=1&a=2&b=3

Nicht, dass ich diese Art der Verwendung von Parameternamen empfehle.

ZZ Coder
quelle
3
Es ist nah an dem, was ich brauche. Das einzige Problem ist, dass es NameValuePair-Objekte erfordert und ich bestenfalls Map.Entry-Objekte habe.
Ula Krukar
1
@Ula: Sie können dies verwenden und tun, Collections2.transform(paramMap.entrySet(), function)wo die Funktion einen Map.Entry verwendet und BasicNameValuePair zurückgibt. (Oder machen Sie dasselbe in einfachem altem Java ohne Google Collections.) Zugegeben, es werden ungefähr 6 zusätzliche Zeilen eigenen Codes benötigt.
Jonik
3
@ Jonik, genau das werde ich tun :) Aber trotzdem bin ich überrascht, dass es nichts gibt, das einfach eine Karte nimmt, scheint so offensichtlich, dass es sollte. Es gibt viele Methoden, die Abfragezeichenfolgen in eine Map verwandeln, und nichts, was das Gegenteil bewirkt.
Ula Krukar
12
@UlaKrukar, weil eine Map <string, string> die falsche Datenstruktur ist. Es behält keine Ordnung bei und erlaubt keine doppelten Schlüssel.
James Moore
2
LinkedHashMap könnte gut genug funktionieren ... URIBuilder von Groovy akzeptiert die Kartenschnittstelle, um queryString zu generieren
Rafael
41

Hier ist etwas, das ich schnell geschrieben habe; Ich bin sicher, dass es verbessert werden kann.

import java.util.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class MapQuery {
    static String urlEncodeUTF8(String s) {
        try {
            return URLEncoder.encode(s, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new UnsupportedOperationException(e);
        }
    }
    static String urlEncodeUTF8(Map<?,?> map) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<?,?> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(String.format("%s=%s",
                urlEncodeUTF8(entry.getKey().toString()),
                urlEncodeUTF8(entry.getValue().toString())
            ));
        }
        return sb.toString();       
    }
    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("p1", 12);
        map.put("p2", "cat");
        map.put("p3", "a & b");         
        System.out.println(urlEncodeUTF8(map));
        // prints "p3=a+%26+b&p2=cat&p1=12"
    }
}
Polygenschmierstoffe
quelle
11
Es ist nicht schwierig, eigene Texte zu schreiben. Die Frage ist, wo, wenn überhaupt, eine Standardversion verfügbar ist.
Skaffman
@ZZCoder: Ja, darüber habe ich mich gewundert. Noch etwas? Ich werde bei der nächsten Überarbeitung anhand von Vorschlägen ein Massenupdate durchführen.
Polygenschmierstoffe
1
@skaffman: Ja, das habe ich später gesehen, nachdem ich es geschrieben habe, aber jetzt, wo ich es bereits getan habe, könnte ich diese Gelegenheit genauso gut nutzen, um andere Leute meinen Code überprüfen und verbessern zu lassen usw. Wenn nicht für OP, wird es Nutzen Sie immer noch mir und vielleicht auch einigen anderen.
Polygenschmierstoffe
5
Die Parameterkarte wird normalerweise als Map<String, List<String>>oder dargestellt Map<String, String[]>. Sie können nämlich mehrere Werte für denselben Namen haben. Ich bin mir nicht sicher, ob OP klug war, es so zu gestalten.
BalusC
@ BalusC: Danke für die Info. Wie sollte dies (wenn es für eine Bibliothek geschrieben wurde) mit nullSchlüsseln / Werten umgehen ? Im Moment die toString()Ursachen NullPointerException, und ich begründe es damit, dass ich "ausfallsicher" sage, aber ich bin mir nicht sicher, wie das mit Leuten gehen würde, die nur wollen, dass die Dinge "funktionieren".
Polygenschmierstoffe
36

Ich fand eine glatte Lösung mit Java 8 und Polygenelubricants 'Solution.

parameters.entrySet().stream()
    .map(p -> urlEncodeUTF8(p.getKey()) + "=" + urlEncodeUTF8(p.getValue()))
    .reduce((p1, p2) -> p1 + "&" + p2)
    .orElse("");
Finsternis
quelle
1
Das hat perfekt für mich funktioniert, danke. Alles, was ich tun musste, war ?an den von diesem zurückgegebenen String anzuhängen und an meine URL anzuhängen
yiwei
1
Wirf eine zusätzliche "Karte" vor die "orElse ()": .map (s -> "?" + S)
sbzoom
3
Was ist mit Array? toto[]=4&toto[]=5
Thomas Decaux
Das ist eine sehr gute Lösung und hilft mir sehr. Vielen Dank.
Deadpool
21

In Spring Util gibt es einen besseren Weg ..,

import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("key", key);
params.add("storeId", storeId);
params.add("orderId", orderId);
UriComponents uriComponents =     UriComponentsBuilder.fromHttpUrl("http://spsenthil.com/order").queryParams(params).build();
ListenableFuture<ResponseEntity<String>> responseFuture =     restTemplate.getForEntity(uriComponents.toUriString(), String.class);
Senthil Arumugam SP
quelle
1
queryParams ist prive: /
user3364192
@ user3364192 Verwenden Sie dieUriComponentsBuilder
MaxZoom
Ich habe die obige Lösung mit einigen Nullwerten in der MultiValueMap ausprobiert, die ich vor dem Aufruf weglassen wollte UriComponentsBuilder.fromHttpUrl. Als ich hinzufügte params.values().removeIf(entry -> entry.getValue() == null);, funktionierte es nicht. Weiß jemand, warum ich Einträge mit der obigen Syntax nicht aus einer MultiValueMap entfernen kann?
Orby
Dies beantwortet das OP nicht, da die Frage war, wie das Mapin Stringein UriComponentsObjekt umgewandelt werden soll , nicht in ein Objekt. Dies ist jedoch nützlich für Personen, die nur die http-Anfrage stellen möchten.
Axiopistie
19

Update Juni 2016

Ich fühlte mich gezwungen, eine Antwort hinzuzufügen, nachdem ich viel zu viele SOF-Antworten mit datierten oder unzureichenden Antworten auf sehr häufige Probleme gesehen hatte - eine gute Bibliothek und eine solide Beispielverwendung für beide parseund formatOperationen.

Verwenden Sie die Bibliothek org.apache.httpcomponents.httpclient . Die Bibliothek enthält das Dienstprogramm org.apache.http.client.utils.URLEncodedUtils .

Zum Beispiel ist es einfach, diese Abhängigkeit von Maven herunterzuladen:

 <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5</version>
 </dependency>

Für meine Zwecke musste ich nur parse(von Abfragezeichenfolge zu Name-Wert-Paaren formatlesen ) und (von Name-Wert-Paaren zu Abfragezeichenfolge lesen) Abfragezeichenfolgen. Es gibt jedoch Äquivalente, um dasselbe mit einem URI zu tun (siehe auskommentierte Zeile unten).

// Erforderliche Importe

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

// Code-Auszug

public static void parseAndFormatExample() throws UnsupportedEncodingException {
        final String queryString = "nonce=12345&redirectCallbackUrl=http://www.bbc.co.uk";
        System.out.println(queryString);
        // => nonce=12345&redirectCallbackUrl=http://www.bbc.co.uk

        final List<NameValuePair> params =
                URLEncodedUtils.parse(queryString, StandardCharsets.UTF_8);
        // List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), "UTF-8");

        for (final NameValuePair param : params) {
            System.out.println(param.getName() + " : " + param.getValue());
            // => nonce : 12345
            // => redirectCallbackUrl : http://www.bbc.co.uk
        }

        final String newQueryStringEncoded =
                URLEncodedUtils.format(params, StandardCharsets.UTF_8);


        // decode when printing to screen
        final String newQueryStringDecoded =
                URLDecoder.decode(newQueryStringEncoded, StandardCharsets.UTF_8.toString());
        System.out.println(newQueryStringDecoded);
        // => nonce=12345&redirectCallbackUrl=http://www.bbc.co.uk
    }

Diese Bibliothek hat genau das getan, was ich brauchte, und konnte gehackten benutzerdefinierten Code ersetzen.

arcseldon
quelle
10

Wenn Sie tatsächlich einen vollständigen URI erstellen möchten, versuchen Sie URIBuilder von Apache Http Compoments (HttpClient 4).

Dies beantwortet die Frage nicht wirklich, aber es beantwortet die Frage, die ich hatte, als ich diese Frage fand.

Thraidh
quelle
10

Ich wollte auf der Antwort von @ eclipse mit Java 8-Mapping und -Reduzierung aufbauen.

 protected String formatQueryParams(Map<String, String> params) {
      return params.entrySet().stream()
          .map(p -> p.getKey() + "=" + p.getValue())
          .reduce((p1, p2) -> p1 + "&" + p2)
          .map(s -> "?" + s)
          .orElse("");
  }

Die zusätzliche mapOperation nimmt die reduzierte Zeichenfolge und setzt ?nur dann eine vor, wenn die Zeichenfolge vorhanden ist.

sbzoom
quelle
Dies funktioniert, aber die Ordnung ist schlecht durcheinander, was im wirklichen Leben kein Problem ist, außer bei Tests, die behaupten, dass () mit Sicherheit ein Schmerz sein wird.
M_F
7

Eine andere "eine Klasse" / keine Abhängigkeitsmethode, die mit Single / Multiple umgeht:

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class UrlQueryString {
  private static final String DEFAULT_ENCODING = "UTF-8";

  public static String buildQueryString(final LinkedHashMap<String, Object> map) {
    try {
      final Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
      final StringBuilder sb = new StringBuilder(map.size() * 8);
      while (it.hasNext()) {
        final Map.Entry<String, Object> entry = it.next();
        final String key = entry.getKey();
        if (key != null) {
          sb.append(URLEncoder.encode(key, DEFAULT_ENCODING));
          sb.append('=');
          final Object value = entry.getValue();
          final String valueAsString = value != null ? URLEncoder.encode(value.toString(), DEFAULT_ENCODING) : "";
          sb.append(valueAsString);
          if (it.hasNext()) {
            sb.append('&');
          }
        } else {
          // Do what you want...for example:
          assert false : String.format("Null key in query map: %s", map.entrySet());
        }
      }
      return sb.toString();
    } catch (final UnsupportedEncodingException e) {
      throw new UnsupportedOperationException(e);
    }
  }

  public static String buildQueryStringMulti(final LinkedHashMap<String, List<Object>> map) {
    try {
      final StringBuilder sb = new StringBuilder(map.size() * 8);
      for (final Iterator<Entry<String, List<Object>>> mapIterator = map.entrySet().iterator(); mapIterator.hasNext();) {
        final Entry<String, List<Object>> entry = mapIterator.next();
        final String key = entry.getKey();
        if (key != null) {
          final String keyEncoded = URLEncoder.encode(key, DEFAULT_ENCODING);
          final List<Object> values = entry.getValue();
          sb.append(keyEncoded);
          sb.append('=');
          if (values != null) {
            for (final Iterator<Object> listIt = values.iterator(); listIt.hasNext();) {
              final Object valueObject = listIt.next();
              sb.append(valueObject != null ? URLEncoder.encode(valueObject.toString(), DEFAULT_ENCODING) : "");
              if (listIt.hasNext()) {
                sb.append('&');
                sb.append(keyEncoded);
                sb.append('=');
              }
            }
          }
          if (mapIterator.hasNext()) {
            sb.append('&');
          }
        } else {
          // Do what you want...for example:
          assert false : String.format("Null key in query map: %s", map.entrySet());
        }
      }
      return sb.toString();
    } catch (final UnsupportedEncodingException e) {
      throw new UnsupportedOperationException(e);
    }
  }

  public static void main(final String[] args) {
    // Examples: could be turned into unit tests ...
    {
      final LinkedHashMap<String, Object> queryItems = new LinkedHashMap<String, Object>();
      queryItems.put("brand", "C&A");
      queryItems.put("count", null);
      queryItems.put("misc", 42);
      final String buildQueryString = buildQueryString(queryItems);
      System.out.println(buildQueryString);
    }
    {
      final LinkedHashMap<String, List<Object>> queryItems = new LinkedHashMap<String, List<Object>>();
      queryItems.put("usernames", new ArrayList<Object>(Arrays.asList(new String[] { "bob", "john" })));
      queryItems.put("nullValue", null);
      queryItems.put("misc", new ArrayList<Object>(Arrays.asList(new Integer[] { 1, 2, 3 })));
      final String buildQueryString = buildQueryStringMulti(queryItems);
      System.out.println(buildQueryString);
    }
  }
}

Sie können bei Bedarf entweder einfach (in den meisten Fällen einfacher zu schreiben) oder mehrfach verwenden. Beachten Sie, dass beide durch Hinzufügen eines kaufmännischen Und kombiniert werden können ... Wenn Sie Probleme haben, lassen Sie es mich in den Kommentaren wissen.

Christophe Roussy
quelle
5

Dies ist die Lösung, die ich mit Java 8 und implementiert habe org.apache.http.client.URLEncodedUtils. Es ordnet die Einträge der Karte einer Liste von zu BasicNameValuePairund verwendet dann Apaches URLEncodedUtils, um daraus eine Abfragezeichenfolge zu machen.

List<BasicNameValuePair> nameValuePairs = params.entrySet().stream()
   .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
   .collect(Collectors.toList());

URLEncodedUtils.format(nameValuePairs, Charset.forName("UTF-8"));
Marlon Bernardes
quelle
Hat perfekt funktioniert! Musste nur eine Abhängigkeit ziehen:compile 'org.apache.httpcomponents:httpclient:4.5.2'
Miguel Reyes
Könnten Sie erklären, was in Ihrem ersten Codeblock vor sich geht, der die an die URLEncodedUtils übergebenen nameValuePairs erstellt? ?
Silversunhunter
2

Verwenden von EntrySet und Streams:

map
  .entrySet()
  .stream()
  .map(e -> e.getKey() + "=" + e.getValue())
  .collect(Collectors.joining("&"));
SIVA KUMAR
quelle
2

Sie können a Streamdafür verwenden, aber anstatt selbst Abfrageparameter anzuhängen, würde ich a verwenden Uri.Builder. Zum Beispiel:

final Map<String, String> map = new HashMap<>();
map.put("param1", "cat");
map.put("param2", "12");

final Uri uri = 
    map.entrySet().stream().collect(
        () -> Uri.parse("relativeUrl").buildUpon(),
        (builder, e) -> builder.appendQueryParameter(e.getKey(), e.getValue()),
        (b1, b2) -> { throw new UnsupportedOperationException(); }
    ).build();

//Or, if you consider it more readable...
final Uri.Builder builder = Uri.parse("relativeUrl").buildUpon();
map.entrySet().forEach(e -> builder.appendQueryParameter(e.getKey(), e.getValue())
final Uri uri = builder.build();

//...    

assertEquals(Uri.parse("relativeUrl?param1=cat&param2=12"), uri);
PPartisan
quelle
2

Ich denke, dies ist besser für die Speichernutzung und Leistung, und ich möchte nur den Eigenschaftsnamen senden, wenn der Wert null ist.

public static String toUrlEncode(Map<String, Object> map) {
    StringBuilder sb = new StringBuilder();
    map.entrySet().stream()
            .forEach(entry
                    -> (entry.getValue() == null
                    ? sb.append(entry.getKey())
                    : sb.append(entry.getKey())
                            .append('=')
                            .append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)))
                    .append('&')
            );
    sb.delete(sb.length() - 1, sb.length());
    return sb.toString();
}
Daniel De León
quelle
Sie könnten wahrscheinlich einen Tischler anstelle von sb.delete () verwenden
Jonathan S. Fisher
2

In Java ist nichts eingebaut, um dies zu tun. Aber, hey, Java ist eine Programmiersprache, also ... programmieren wir es!

map.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"))

Dies gibt Ihnen "param1 = 12 & param2 = cat". Jetzt müssen wir die URL und dieses Bit zusammenfügen. Sie würden denken, Sie können einfach tun:URL + "?" + theAbove Wenn die URL jedoch bereits ein Fragezeichen enthält, müssen Sie stattdessen alles mit "&" verbinden. Eine Möglichkeit, dies zu überprüfen, besteht darin, festzustellen, ob die URL bereits irgendwo ein Fragezeichen enthält.

Außerdem weiß ich nicht genau, was auf Ihrer Karte steht. Wenn es sich um Rohstoffe handelt, müssen Sie den Anruf an e.getKey()und e.getValue()mit URLEncoder.encodeoder ähnlichem wahrscheinlich absichern .

Ein weiterer Weg ist, dass Sie eine breitere Sichtweise haben. Versuchen Sie, den Inhalt einer Karte an eine URL anzuhängen, oder ... versuchen Sie, eine HTTP (S) -Anforderung von einem Java-Prozess mit den Inhalten in der Karte als (zusätzliche) HTTP-Parameter zu stellen? Im letzteren Fall können Sie in eine http-Bibliothek wie OkHttp schauen, die einige nette APIs hat, um diesen Job zu erledigen. Dann können Sie zunächst auf die Notwendigkeit verzichten, mit dieser URL herumzuspielen .

rzwitserloot
quelle
1

Um die Antwort von @ eclipse ein wenig zu verbessern: In Javaland wird eine Anforderungsparameterzuordnung normalerweise als eine Map<String, String[]>, eine Map<String, List<String>>oder möglicherweise eine Art davon dargestellt, MultiValueMap<String, String>die ungefähr dasselbe ist. In jedem Fall: Ein Parameter kann normalerweise mehrere Werte haben. Eine Java 8-Lösung wäre daher ungefähr so:

public String getQueryString(HttpServletRequest request, String encoding) {
    Map<String, String[]> parameters = request.getParameterMap();

    return parameters.entrySet().stream()
            .flatMap(entry -> encodeMultiParameter(entry.getKey(), entry.getValue(), encoding))
            .reduce((param1, param2) -> param1 + "&" + param2)
            .orElse("");
}

private Stream<String> encodeMultiParameter(String key, String[] values, String encoding) {
    return Stream.of(values).map(value -> encodeSingleParameter(key, value, encoding));
}

private String encodeSingleParameter(String key, String value, String encoding) {
    return urlEncode(key, encoding) + "=" + urlEncode(value, encoding);
}

private String urlEncode(String value, String encoding) {
    try {
        return URLEncoder.encode(value, encoding);
    } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Cannot url encode " + value, e);
    }
}
Auke
quelle
0

Persönlich würde ich mich für eine Lösung wie diese entscheiden, die der von @rzwitserloot bereitgestellten Lösung unglaublich ähnlich ist, nur subtile Unterschiede.

Diese Lösung ist klein, einfach und sauber. Sie erfordert nur sehr wenig Abhängigkeiten, die alle Teil des Java Util-Pakets sind.

Map<String, String> map = new HashMap<>();

map.put("param1", "12");
map.put("param2", "cat");

String output = "someUrl?";
output += map.entrySet()
    .stream()
    .map(x -> x.getKey() + "=" + x.getValue() + "&")
    .collect(Collectors.joining("&"));

System.out.println(output.substring(0, output.length() -1));
JO3-W3B-D3V
quelle
0

Für eine mehrwertige Karte können Sie wie folgt vorgehen (mit Java 8 Stream APIs)

Die URL-Codierung wurde dabei berücksichtigt.

MultiValueMap<String, String> params =  new LinkedMultiValueMap<>();
String urlQueryString = params.entrySet()
            .stream()
            .flatMap(stringListEntry -> stringListEntry.getValue()
                    .stream()
                    .map(s -> UriUtils.encode(stringListEntry.getKey(), StandardCharsets.UTF_8.toString()) + "=" +
                            UriUtils.encode(s, StandardCharsets.UTF_8.toString())))
            .collect(Collectors.joining("&"));
Robin Mathur
quelle
0

Kotlin

mapOf(
  "param1" to 12,
  "param2" to "cat"
).map { "${it.key}=${it.value}" }
  .joinToString("&")
Changhoon
quelle