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


77

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

例:

我有一张地图:

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

我想得到:

param1=12&param2=cat

最终输出

relativeUrl+param1=12&param2=cat

好问题。如果没有实用程序,Kinda确实会感到惊讶……我找不到快速的方法。
尼克


Answers:


68

我看到的最可靠的组件是Apache Http Compoments(HttpClient 4.0)的URLEncodedUtils类。

该方法URLEncodedUtils.format()是您所需要的。

它不使用map,因此您可以使用重复的参数名称,例如,

  a=1&a=2&b=3

并非我建议使用这种类型的参数名称。


3
它接近我的需求。唯一的问题是,它需要NameValuePair对象,而我所拥有的只是Map.Entry对象。
乌拉克鲁卡

1
@Ula:您可以使用此方法,并Collections2.transform(paramMap.entrySet(), function)在函数接受Map.Entry并返回BasicNameValuePair的情况下执行此操作。(或者在没有Google Collections的普通Java中也做同样的事情。)确实,大约需要多出6行自己的代码。
约尼克

3
@Jonik,这正是我会做的:)但仍然令我感到惊讶的是,没有什么东西可以简单地绘制地图,似乎如此之明显。有很多方法可以将查询字符串转换为映射,而没有相反的方法。
乌拉克鲁卡

12
@UlaKrukar,这是因为map <string,string>是错误的数据结构。它不会保留顺序,也不允许重复的键。
詹姆斯·摩尔

2
LinkedHashMap可能工作得足够好……Groovy的URIBuilder接受Map接口生成queryString
Rafael

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,2010年

@ZZCoder:是的,我对此很纳闷。还要别的吗?我将根据建议在下一个修订版中进行大量更新。
polygenelubricants 2010年

1
@skaffman:是的,我在写完它之后就看到了,但是无论如何,既然我已经这样做了,我不妨利用这个机会让其他人来检查我的代码并对其进行改进等。如果不是用于OP,它将仍然使我受益,也许还有其他好处。
polygenelubricants 2010年

5
参数映射通常表示为Map<String, List<String>>Map<String, String[]>。您可以为同一个名称拥有多个值。不确定OP是否以这种方式设计是否明智。
BalusC,2010年

@BalusC:感谢您提供信息。顺便说一句,这(如果是为库编写的)应该如何处理null键/值?现在,toString()原因是什么NullPointerException,我通过说“ fail-fast”来证明这一点是正确的,但是我不确定那些只希望事情“起作用”的人会怎么做。
polygenelubricants 2010年

36

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

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

1
这对我来说是完美的工作,谢谢,我要做的就是将其附加?到返回的字符串中并附加到我的网址上
-yiwei

1
在“ orElse()”之前插入一个额外的“地图”:.map(s->“?” + s)
sbzoom

3
数组呢?toto[]=4&toto[]=5
Thomas Decaux

这是非常好的解决方案,对我有很大帮助。谢谢。
死侍

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是prive:/
user3364192 '16

@ user3364192使用UriComponentsBuilder
MaxZoom

我尝试在MultiValueMap中使用一些空值尝试上述解决方案,但在调用之前我想将其忽略UriComponentsBuilder.fromHttpUrl。当我添加时params.values().removeIf(entry -> entry.getValue() == null);,它不起作用。有人知道为什么我无法使用上述语法从MultiValueMap中删除条目吗?
奥比

这不能回答OP,因为问题在于如何将转换MapString,而不是UriComponents对象。但这对于只想发出http请求的人很有用。
axiopisty

19

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;

//代码段

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()肯定会很痛苦。
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);
    }
  }
}

您可以根据需要使用简单的(在大多数情况下更容易编写)或多个。请注意,可以通过添加与号来组合两者。如果发现任何问题,请在评论中告知我。


5

这是我使用Java 8和实现的解决方案org.apache.http.client.URLEncodedUtils。它将映射的条目映射到一个列表中BasicNameValuePair,然后使用ApacheURLEncodedUtils将其转换成查询字符串。

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的第一段代码中发生了什么。?
silversunhunter


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

我认为这对于内存使用和性能更好,并且我想仅在属性值为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()
乔纳森·费舍尔

2

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上,还是...您是在试图通过Java进程发出HTTP(S)请求,并将地图中的内容作为(附加)HTTP参数?在后一种情况下,您可以查看一个类似OkHttp的http库,该库具有一些不错的API可以完成此工作,然后您就可以首先避免任何需要对该URL进行操作的麻烦。


1

为了改善@eclipse的答案:在Javaland中,请求参数映射通常表示为a Map<String, String[]>,aMap<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流API)

网址编码已被考虑在内。

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("&")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.