如何将地图转换为 url 查询字符串?

Posted

技术标签:

【中文标题】如何将地图转换为 url 查询字符串?【英文标题】:How to convert map to url query string? 【发布时间】:2011-02-18 02:06:21 【问题描述】:

您知道任何可以将 Map 转换为 URL 友好查询字符串的实用程序类/库吗?

例子:

我有一张地图:

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

我想得到:

param1=12&param2=cat

最终输出

relativeUrl+param1=12&param2=cat

【问题讨论】:

好问题。如果没有实用程序,这确实有点令人惊讶……我找不到一个。 java.dzone.com/articles/two-ways-convert-java-map ***.com/questions/2809877/… 【参考方案1】:

这是我很快写的;我相信它可以改进。

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"
    

【讨论】:

自己编写并不难,问题是哪里有现成的版本。 @ZZCoder:是的,我想知道这一点。还要别的吗?我会根据建议对下一个版本进行大规模更新。 @skaffman:是的,我写完之后就看到了,但无论如何,既然我已经这样做了,我不妨利用这个机会让其他人审查我的代码并改进它等等。如果不是 OP,它仍然对我有好处,也许对其他一些人也有好处。 参数映射通常表示为Map&lt;String, List&lt;String&gt;&gt;Map&lt;String, String[]&gt;。您可以为同一个名称设置多个值。不知道 OP 这样设计是否聪明。 @BalusC:感谢您的信息。顺便说一句,这(如果它是为库编写的)应该如何处理null 键/值?现在toString() 导致NullPointerException,我通过说“快速失败”来证明这一点,但我不确定对于那些只想让事情“工作”的人来说会如何。【参考方案2】:

我看到的现成的最强大的一个是来自Apache Http Compoments (HttpClient 4.0) 的URLEncodedUtils 类。

方法URLEncodedUtils.format()是你需要的。

它不使用映射,所以你可以有重复的参数名称,比如,

  a=1&a=2&b=3

并不是我推荐这种使用参数名称的方式。

【讨论】:

接近我需要的。唯一的问题是它需要 NameValuePair 对象,而我拥有的充其量是 Map.Entry 对象。 @Ula:您可以使用它并执行Collections2.transform(paramMap.entrySet(), function),其中函数采用 Map.Entry 并返回 BasicNameValuePair。 (或者在没有 Google Collections 的普通 Java 中做同样的事情。)当然,需要额外的 6 行自己的代码。 @Jonik,这正是我将要做的 :) 但我仍然感到惊讶的是,没有任何东西可以简单地使用地图,看起来应该如此明显。有很多方法可以将查询字符串转换为地图,而没有任何相反的方法。 @UlaKrukar,这是因为 map 是使用错误的数据结构。它不保留顺序,也不允许重复键。 LinkedHashMap 可以很好地工作......来自 Groovy 的 URIBuilder 接受 Map 接口来生成 queryString【参考方案3】:

如果您确实想构建一个完整的 URI,请尝试从 Apache Http Compoments (HttpClient 4) 转换为 URIBuilder。

这实际上并没有回答这个问题,但它回答了我发现这个问题时的那个问题。

【讨论】:

【参考方案4】:

另一种“单类”/无依赖方式,处理单/多:

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

您可以在需要时使用简单(在大多数情况下更容易编写)或多个。请注意,两者都可以通过添加一个&符号来组合...... 如果您发现任何问题,请在 cmets 中告诉我。

【讨论】:

【参考方案5】:

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

【讨论】:

queryParams 是私有的:/ @user3364192 使用UriComponentsBuilder 我尝试了上述解决方案,在 MultiValueMap 中有一些空值,我想在调用 UriComponentsBuilder.fromHttpUrl 之前省略这些值。当我添加params.values().removeIf(entry -&gt; entry.getValue() == null); 时,它不起作用。任何人都知道为什么我不能使用上述语法从 MultiValueMap 中删除条目? 这没有回答 OP,因为问题是如何将 Map 转换为 String,而不是 UriComponents 对象。但这对于只想发出 http 请求的人很有用。【参考方案6】:

我找到了一个使用 java 8 和 polygenelubricants 解决方案的平滑解决方案。

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

【讨论】:

这对我来说非常有效,谢谢,我所要做的就是将 ? 附加到从这里返回的字符串并附加到我的网址 在 "orElse()" 之前添加一个额外的 "map": .map(s -> "?" + s) 数组呢? toto[]=4&amp;toto[]=5 这是一个很好的解决方案,对我帮助很大。谢谢。 Java 10 引入了URLEncoder.encode(String s, Charset charset),它不再抛出UnsupportedEncodingException,因此可以在不调用辅助方法的情况下简化映射调用【参考方案7】:

这是我使用 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' 您能否解释一下在创建传递给 URLEncodedUtils 的 nameValuePairs 的第一个代码块中发生了什么。 ?【参考方案8】:

为了改进@eclipse 的回答:在Javaland 中,请求参数映射通常表示为Map&lt;String, String[]&gt;Map&lt;String, List&lt;String&gt;&gt; 或可能是某种MultiValueMap&lt;String, String&gt;,这有点类似。无论如何:一个参数通常可以有多个值。因此,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);
    

【讨论】:

【参考方案9】:

2016 年 6 月更新

觉得不得不添加一个答案,因为看到太多 SOF 答案,但对非常常见的问题的答案过时或不充分 - 一个很好的库和一些用于 parseformat 操作的可靠示例。

使用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;

//代码sn-p

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】:

我想使用 java 8 映射和归约来构建 @eclipse 的答案。

 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() 肯定会很痛苦。 如果params值包含特殊字符,不编码会产生问题【参考方案11】:

java 中没有内置的东西可以做到这一点。但是,嘿,java 是一种编程语言,所以.. 让我们编程吧!

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

这会给你“param1=12&param2=cat”。现在我们需要将 URL 和这个位连接在一起。你认为你可以这样做:URL + "?" + theAbove,但如果 URL 已经包含一个问号,你必须用“&”代替。一种检查方法是查看 URL 中是否已经存在问号。

另外,我不太清楚你的地图上有什么。如果是原始内容,您可能必须使用 URLEncoder.encode 或类似名称保护对 e.getKey()e.getValue() 的调用。

另一种方法是放宽视野。您是否尝试将地图的内容附加到 URL,或者...您是否尝试使用地图中的内容作为(附加)HTTP 参数从 Java 进程发出 HTTP(S) 请求?在后一种情况下,您可以查看像 OkHttp 这样的 http 库,它有一些很好的 API 可以完成这项工作,然后您可以放弃任何需要首先处理该 URL。

【讨论】:

【参考方案12】:

就个人而言,我会选择这样的解决方案,它与@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));

【讨论】:

【参考方案13】:

使用 EntrySet 和 Streams:

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

【讨论】:

【参考方案14】:

您可以为此使用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);

【讨论】:

【参考方案15】:

我认为这对内存使用和性能更好,并且我想在值为 null 时只发送属性名称。

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()【参考方案16】:

对于多值映射,您可以执行以下操作(使用 java 8 流 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("&"));

【讨论】:

【参考方案17】:

科特林

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

【讨论】:

您可能希望对键和值进行 url 编码

以上是关于如何将地图转换为 url 查询字符串?的主要内容,如果未能解决你的问题,请参考以下文章

怎样把查询转换为url

如何在 Clojure/Compojure/Ring 中将映射转换为 URL 查询字符串?

如何将这些坐标转换为谷歌地图可读的坐标?

如何通过 url 中的查询字符串添加苹果地图标记?

将 javascript 对象转换为 url 查询

如何使用 ASP.NET Core 将查询字符串参数转换为看起来像目录部分?