Как преобразовать карту в строку запроса URL? - PullRequest
60 голосов
/ 11 мая 2010

Известны ли вам какие-либо служебные классы / библиотеки, которые могут преобразовать Map в строку запроса, удобную для URL?

Пример:

У меня есть карта:

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

Я хочу получить:

param1=12&param2=cat

конечный результат

relativeUrl+param1=12&param2=cat

Ответы [ 14 ]

60 голосов
/ 11 мая 2010

Самым надежным из всех, что я видел в продаже, является URLEncodedUtils класс из Apache Http Compoments (HttpClient 4.0).

Метод URLEncodedUtils.format() - это то, что вам нужно.

Он не использует карту, поэтому вы можете иметь повторяющиеся имена параметров, например,

  a=1&a=2&b=3

Не то чтобы я рекомендовал этот вид использования имен параметров.

40 голосов
/ 11 мая 2010

Вот кое-что, что я быстро написал; Я уверен, что это можно улучшить.

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"
    }
}
26 голосов
/ 23 марта 2015

Я нашел гладкое решение, используя java 8 и раствор полигенных смазок.

parameters.entrySet().stream()
    .map(p -> urlEncodeUTF8(p.getKey()) + "=" + urlEncodeUTF8(p.getValue()))
    .reduce((p1, p2) -> p1 + "&" + p2)
    .orElse("");
14 голосов
/ 10 июня 2016

Обновление июнь 2016

Чувствовал себя вынужденным добавить ответ, увидев слишком много ответов SOF с датированными или неадекватными ответами на очень распространенную проблему - хорошую библиотеку и несколько убедительных примеров использования для операций parse и format.

Используйте библиотеку org.apache.httpcomponents.httpclient . Библиотека содержит эту org.apache.http.client.utils.URLEncodedUtils утилиту класса.

Например, легко загрузить эту зависимость из Maven:

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

Для моих целей мне нужно было только parse (чтение из строки запроса в пары имя-значение) и format (чтение из пары имя-значение в строку запроса) строки запроса. Однако есть эквиваленты для того же самого с URI (см. Закомментированную строку ниже).

// Требуемый импорт

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;

// фрагмент кода

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
    }

Эта библиотека сделала именно то, что мне было нужно, и смогла заменить взломанный пользовательский код.

13 голосов
/ 14 августа 2014

В Spring Util есть лучший способ ..,

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);
10 голосов
/ 08 ноября 2012

Если вы действительно хотите создать полный URI, попробуйте URIBuilder из Apache Http Compoments (HttpClient 4).

На самом деле это не отвечает на вопрос, но он ответил на тот, который был у меня, когда я нашел этот вопрос.

7 голосов
/ 01 июля 2017

Я хотел построить ответ @ eclipse, используя сопоставление java 8 и сокращение.

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

}

Дополнительная операция map берет сокращенную строку и помещает ? вперед, только если строка существует.

7 голосов
/ 18 октября 2013

Еще один «один класс» / нет зависимости способ сделать это, обрабатывая один / несколько:

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

Вы можете использовать простой (в большинстве случаев легче писать) или множественный при необходимости. Обратите внимание, что оба могут быть объединены, добавив амперсанд ... Если вы обнаружите какие-либо проблемы, дайте мне знать в комментариях.

4 голосов
/ 14 мая 2015

Это решение, которое я реализовал, используя Java 8 и org.apache.http.client.URLEncodedUtils. Он отображает записи карты в список BasicNameValuePair и затем использует Apache URLEncodedUtils, чтобы превратить это в строку запроса.

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

URLEncodedUtils.format(nameValuePairs, Charset.forName("UTF-8"));
1 голос
/ 19 июня 2019

Для этого вы можете использовать Stream, но вместо добавления параметров запроса я бы использовал Uri.Builder. Например:

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