Як перетворити карту в рядок запиту url?


79

Чи знаєте ви про якийсь утилітний клас / бібліотеку, який може перетворити Map у рядок запитів, зручний для URL-адрес?

Приклад:

У мене є карта:

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

Я хочу отримати:

param1=12&param2=cat

кінцевий результат

relativeUrl+param1=12&param2=cat

Гарне питання. Справді дивно, якщо для цього немає ніякої утиліти ... Я не зміг швидко знайти її.
Джонік


Відповіді:


68

Найбільш надійним, який я побачив у продажу, є клас URLEncodedUtils від Apache Http Compoments (HttpClient 4.0).

Метод URLEncodedUtils.format() - це те, що вам потрібно.

Він не використовує карту, тому ви можете мати повторювані імена параметрів, наприклад,

  a=1&a=2&b=3

Не те, що я рекомендую такий тип використання імен параметрів.


3
Це близько до того, що мені потрібно. Єдина проблема полягає в тому, що для цього потрібні об’єкти NameValuePair, а те, що я маю, - це об’єкти Map.Entry в кращому випадку.
Ула Крукар,

1
@Ula: Ви можете використовувати це і робити Collections2.transform(paramMap.entrySet(), function)там, де функція приймає Map.Entry і повертає BasicNameValuePair. (Або зробіть те ж саме у звичайній старій Java без колекцій Google.) Звичайно, потрібно близько 6 додаткових рядків власного коду.
Джонік

3
@Jonik, це саме те, що я буду робити :) Але все-таки я здивований, що немає нічого, що б просто взяло карту, здається настільки очевидним, що мало б бути. Існує безліч методів, які перетворюють рядок запиту на карту, і нічого не робить протилежного.
Ула Крукар

12
@UlaKrukar, це тому, що map <рядок, рядок> є неправильною структурою даних для використання. Він не зберігає порядок і не дозволяє дублікати ключів.
James Moore

2
LinkedHashMap міг працювати досить добре ... URIBuilder із Groovy приймає інтерфейс Map для створення queryString
Рафаель

41

Ось щось, що я швидко написав; Я впевнений, що це можна вдосконалити.

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

11
Написати власний текст не складно, питання полягає в тому, де, якщо є десь, є готова версія.
skaffman

@ZZCoder: Так, мені це було цікаво. Ще щось? Я буду масово оновлювати наступну версію на основі пропозицій.
полігенмастильні матеріали

1
@skaffman: Так, я бачив це пізніше після того, як я його написав, але в будь-якому випадку, тепер, коли я це вже зробив, я міг би також скористатися цією можливістю, щоб інші люди переглянули мій код і вдосконалили його і т. д. Якби не OP, це буде все ще приносить користь мені, і, можливо, деяким іншим теж.
полігенні мастила

5
Карта параметрів зазвичай представляється як Map<String, List<String>>або Map<String, String[]>. Ви можете мати кілька значень для одного і того ж імені. Не впевнений, чи був OP розумним, щоб спроектувати його таким чином.
BalusC,

@BalusC: дякую за інформацію. До речі, як це (якщо це було написано для бібліотеки) має обробляти nullключі / значення? Зараз toString()причини NullPointerException, і я обгрунтовую це тим, що кажу "швидко вийти з ладу", але я не впевнений, як це буде з людьми, які просто хочуть, щоб речі "спрацювали".
полігенмастильні речовини

36

Я знайшов гладке рішення, використовуючи Java 8 та розчин полігенів-мастил.

parameters.entrySet().stream()
    .map(p -> urlEncodeUTF8(p.getKey()) + "=" + urlEncodeUTF8(p.getValue()))
    .reduce((p1, p2) -> p1 + "&" + p2)
    .orElse("");

1
це мені спрацювало чудово, дякую, все, що мені потрібно було додати ?до рядка, повернутого з цього, і прикріпити до моєї URL-адреси
yiwei

1
Киньте зайву "карту" перед "orElse ()": .map (s -> "?" + S)
sbzoom

3
а як щодо масиву? toto[]=4&toto[]=5
Томас Деко

Це дуже хороше рішення і мені дуже допомагає. Дякую.
CA Arefe

21

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

1
queryParams є приватним: /
user3364192

@ user3364192 ВикористовуйтеUriComponentsBuilder
MaxZoom

Я спробував вищевказане рішення з деякими нульовими значеннями в MultiValueMap, які я хотів опустити перед викликом UriComponentsBuilder.fromHttpUrl. Коли я додав params.values().removeIf(entry -> entry.getValue() == null);, це не спрацювало. Хто-небудь знає, чому я не можу видалити записи з MultiValueMap, використовуючи наведений вище синтаксис?
Orby

Це не відповідає на OP, оскільки питання полягало в тому, як перетворити Mapa String, а не UriComponentsоб'єкт. Але це корисно для людей, які просто хочуть надіслати запит http.
аксіопісти

19

Оновлення в червні 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
    }

Ця бібліотека зробила саме те, що мені потрібно, і змогла замінити якийсь зламаний спеціальний код.


10

Якщо ви насправді хочете створити повний URI, спробуйте URIBuilder від Apache Http Compoments (HttpClient 4).

Це насправді не відповідає на питання, але відповідає на той, який я мав, коли знайшов це запитання.


10

Я хотів побудувати відповідь @ 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операція приймає зменшений рядок і ставить ?фронт, лише якщо рядок існує.


Це працює, але зіпсувати порядок погано, що не є проблемою в реальному житті, за винятком тестів assertThat (), безумовно, буде болем.
M_F

7

Інший `` один клас '' / відсутність залежності, обробка одного / кількох:

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

Ви можете використовувати або простий (простіше писати в більшості випадків), або кілька, коли це потрібно. Зверніть увагу, що обидва варіанти можна поєднати, додавши амперсанд ... Якщо ви виявите якісь проблеми, повідомте мене про це в коментарях.


6

Це рішення я реалізував, використовуючи 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"));

Працювали чудово! Потрібно було взяти лише одну залежність:compile 'org.apache.httpcomponents:httpclient:4.5.2'
Мігель Рейес,

Чи можна б вам пояснити, що відбувається у вашому першому блоці коду, який створює ім'яValuePairs, яке передається URLEncodedUtils. ?
silversunhunter

3

Використання EntrySet та Streams:

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

3

Для цього немає нічого вбудованого в java. Але, ей, Java - це мова програмування, тож .. давайте її запрограмуємо!

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

Це дає вам "param1 = 12 & param2 = cat". Тепер нам потрібно об’єднати URL та цей біт разом. Ви можете подумати, що можете просто зробити:URL + "?" + theAbove але якщо URL-адреса вже містить знак питання, вам доведеться об’єднати все це разом із "&". Один із способів перевірити - перевірити, чи десь в URL-адресі вже є знак питання.

Крім того, я не зовсім знаю, що є на вашій карті. Якщо це сировина, вам, мабуть, доведеться захистити дзвінок до e.getKey()та e.getValue()з URLEncoder.encodeабо подібним.

Ще один шлях - це ширший погляд. Ви намагаєтесь додати вміст карти до URL-адреси, або ... чи намагаєтесь зробити запит HTTP (S) із процесу Java із вмістом на карті у вигляді (додаткових) параметрів HTTP? В останньому випадку ви можете заглянути в бібліотеку http, як OkHttp, яка має кілька приємних API для виконання цієї роботи, тоді ви можете відмовитись від будь-якої необхідності возитися з цією URL-адресою.


2

Ви можете використовувати Streamдля цього a , але замість того, щоб додавати параметри запиту самостійно, я б використав 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);

2

Я думаю, що це краще для використання пам'яті та продуктивності, і я хочу надіслати лише ім'я властивості, коли значення є нульовим.

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

Можливо, ви могли б використати столяра замість sb.delete ()
Джонатан С. Фішер

1

Щоб трохи покращити відповідь @ eclipse: У Javaland карта параметрів запиту зазвичай представляється як a Map<String, String[]>, a Map<String, List<String>>або, можливо, якийсь тип, MultiValueMap<String, String>який є одним і тим же. У будь-якому випадку: параметр зазвичай може мати кілька значень. Тому рішення Java 8 було б таким чином:

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

0

Особисто я піду на таке рішення, воно неймовірно схоже на рішення, надане @rzwitserloot, лише незначні відмінності.

Це рішення невелике, просте та чисте, воно вимагає дуже мало з точки зору залежностей, які всі є частиною пакету Java Util.

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

0

Для багатозначної карти ви можете зробити, як показано нижче (використовуючи Java 8 stream api)

У цьому було взято до уваги кодування URL-адреси.

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

0

Котлін

mapOf(
  "param1" to 12,
  "param2" to "cat"
).map { "${it.key}=${it.value}" }
  .joinToString("&")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.