Do you know of any utility class/library, that can convert Map into URL-friendly query string?
Example:
I have a map:
"param1"=12,
"param2"="cat"
I want to get:
param1=12¶m2=cat
final output
relativeUrl+param1=12¶m2=cat
The most robust one I saw off-the-shelf is the URLEncodedUtils class from Apache Http Compoments (HttpClient 4.0).
The method URLEncodedUtils.format() is what you need.
It doesn't use map so you can have duplicate parameter names, like,
a=1&a=2&b=3
Not that I recommend this kind of use of parameter names.
Here's something that I quickly wrote; I'm sure it can be improved upon.
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"
}
}
I found a smooth solution using java 8 and polygenelubricants' solution.
parameters.entrySet().stream()
.map(p -> urlEncodeUTF8(p.getKey()) + "=" + urlEncodeUTF8(p.getValue()))
.reduce((p1, p2) -> p1 + "&" + p2)
.orElse("");
In Spring Util, there is a better way..,
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);
Update June 2016
Felt compelled to add an answer having seen far too many SOF answers with dated or inadequate answers to very common problem - a good library and some solid example usage for both parse and format operations.
Use org.apache.httpcomponents.httpclient library. The library contains this org.apache.http.client.utils.URLEncodedUtils class utility.
For example, it is easy to download this dependency from Maven:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
For my purposes I only needed to parse (read from query string to name-value pairs) and format (read from name-value pairs to query string) query strings. However, there are equivalents for doing the same with a URI (see commented out line below).
// Required imports
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 snippet
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
}
This library did exactly what I needed and was able to replace some hacked custom code.
If you actually want to build a complete URI, try URIBuilder from Apache Http Compoments (HttpClient 4).
This does not actually answer the question, but it answered the one I had when I found this question.
I wanted to build on #eclipse's answer using java 8 mapping and reducing.
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("");
}
The extra map operation takes the reduced string and puts a ? in front only if the string exists.
Another 'one class'/no dependency way of doing it, handling single/multiple:
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);
}
}
}
You may use either simple (easier to write in most cases) or multiple when required. Note that both can be combined by adding an ampersand...
If you find any problems let me know in the comments.
This is the solution I implemented, using Java 8 and org.apache.http.client.URLEncodedUtils. It maps the entries of the map into a list of BasicNameValuePair and then uses Apache's URLEncodedUtils to turn that into a query string.
List<BasicNameValuePair> nameValuePairs = params.entrySet().stream()
.map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
URLEncodedUtils.format(nameValuePairs, Charset.forName("UTF-8"));
There's nothing built into Java to do this. But, hey, Java is a programming language, so. Let's program it!
map
.entrySet()
.stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"))
This gives you param1=12¶m2=cat. Now we need to join the URL and this bit together. You'd think you can just do: URL + "?" + theAbove but if the URL already contains a question mark, you have to join it all together with "&" instead. One way to check is to see if there's a question mark in the URL someplace already.
Also, I don't quite know what is in your map. If it's raw stuff, you probably have to safeguard the call to e.getKey() and e.getValue() with URLEncoder.encode or similar.
Yet another way to go is that you take a wider view. Are you trying to append a map's content to a URL, or... Are you trying to make an HTTP (S) request from a Java process with the stuff in the map as (additional) HTTP params? In the latter case, you can look into an HTTP library like OkHttp which has some nice APIs to do this job, then you can forego any need to mess about with that URL in the first place.
Using EntrySet and Streams:
map
.entrySet()
.stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
You can use a Stream for this, but instead of appending query parameters myself I'd use a Uri.Builder. For example:
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¶m2=12"), uri);
Here's a simple kotlin solution:
fun Map<String, String>.toUrlParams(): String =
entries.joinToString("&") {
it.key.toUrlEncoded() + "=" + it.value.toUrlEncoded()
}
fun String.toUrlEncoded(): String = URLEncoder.encode(
this, StandardCharsets.UTF_8
)
To improve a little bit upon #eclipse's answer: In Javaland a request parameter map is usually represented as a Map<String, String[]>, a Map<String, List<String>> or possibly some kind of MultiValueMap<String, String> which is sort of the same thing. In any case: a parameter can usually have multiple values. A Java 8 solution would therefore be something along these lines:
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);
}
}
If you need just the query string (not the whole URL) and you are using Spring Framework, you can do this:
import org.springframework.web.util.UriComponentsBuilder;
...
final String queryString = UriComponentsBuilder.newInstance()
.queryParam("many", "7", "14", "21")
.queryParam("single", "XYZ")
.build()
.toUri()
.getQuery();
System.out.println(queryString);
the result is:
many=7&many=14&many=21&single=XYZ
I make these functions than also send just the property name when the value is null.
public static String urlEncode(Map<?, ?> map) {
return map.entrySet().stream().map(
entry -> entry.getValue() == null
? urlEncode(entry.getKey())
: urlEncode(entry.getKey()) + "=" + urlEncode(entry.getValue())
).collect(Collectors.joining("&"));
}
public static String urlEncode(Object obj) {
return URLEncoder.encode(obj.toString(), StandardCharsets.UTF_8);
}
For multivalue map you can do like below (using java 8 stream api's)
Url encoding has been taken cared in this.
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("&"));
Personally, I'd go for a solution like this, it's incredibly similar to the solution provided by #rzwitserloot, only subtle differences.
This solution is small, simple & clean, it requires very little in terms of dependencies, all of which are a part of the Java Util package.
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));
Kotlin
mapOf(
"param1" to 12,
"param2" to "cat"
).map { "${it.key}=${it.value}" }
.joinToString("&")
a very lightweight answer it works for me
public static String queryStr(Map<String, String> data) throws UnsupportedEncodingException {
StringBuilder query = new StringBuilder();
for (Entry<String, String> entry : data.entrySet()) {
if (query.length() > 0) {
query.append('&');
}
query.append(entry.getKey()).append('=');
query.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
return query.toString();
}
I have a Four different enum UserOneStatus, UserTwoStatus, UserThreeType and UserFourType which has ID and Name
I have to filter the User based on the those Enum ID's so, I end up with the following code
if (ChoosenUserFilterValue.equals(String.valueOf(UserOneStatus.REQUESTED.getId()))) {
selecedUserFilter = new UserFilter(UserOneStatus.class,
String.valueOf(UserOneStatus.REQUESTED.getId()),
UserOneStatus.REQUESTED.getUserStatus(), String.valueOf(UUID.randomUUID()));
} else if (ChoosenUserFilterValue.equals(UserOneStatus.ACTIVE.getId() + "")) {
selecedUserFilter = new UserFilter(UserOneStatus.class,
String.valueOf(UserOneStatus.ACTIVE.getId()),
UserOneStatus.ACTIVE.getUserStatus(), String.valueOf(UUID.randomUUID()));
} else if (ChoosenUserFilterValue.equals(UserOneStatus.INACTIVE.getId() + "")) {
selecedUserFilter = new UserFilter(UserOneStatus.class,
String.valueOf(UserOneStatus.INACTIVE.getId()),
UserOneStatus.INACTIVE.getUserStatus(), String.valueOf(UUID.randomUUID()));
} else if (ChoosenUserFilterValue.equals(UserTwoStatus.REVOKED.getId() + "")) {
selecedUserFilter = new UserFilter(UserTwoStatus.class,
String.valueOf(UserTwoStatus.REVOKED.getId()),
UserTwoStatus.REVOKED.getLoginStatus(), String.valueOf(UUID.randomUUID()));
} else if (ChoosenUserFilterValue.equals(UserThreeType .ADMIN.getId() + "")) {
selecedUserFilter = new UserFilter(Some.class,
String.valueOf(UserThreeType.ADMIN.getId()),
UserThreeType.ADMIN.getName(), String.valueOf(UUID.randomUUID()));
} else if (ChoosenUserFilterValue.equals(UserFourType .SSO_TEMPLATE.getId() + "")) {
selecedUserFilter = new UserFilter(Some.class,
String.valueOf(UserFourType.SSO_TEMPLATE.getId()),
UserFourType.TEMPLATE.getName(), String.valueOf(UUID.randomUUID()));
}
I refactored It little bit but I could't able to get the results as same as the upper code
The refactored code is as follows
Map<String,Class<?>> map = new HashMap<String, Class<?>>();
map.put(UserOneStatus.REQUESTED.getId()+"", UserOneStatus.class);
map.put(UserOneStatus.ACTIVE.getId()+"", UserOneStatus.class);
map.put(UserOneStatus.INACTIVE.getId()+"", UserOneStatus.class);
map.put(UserTwoStatus.REVOKED.getId()+"", UserTwoStatus.class);
map.put(UserThreeType.ADMIN.getId()+"", Some.class);
map.put(UserFourType.TEMPLATE.getId()+"", Some.class);
String key ="";
if(map.containsKey(ChoosenUserFilterValue)){
Class<?> getSelectedUserFilterValueClass = map.get(selectedUserFilterValue);
for (Entry<String, Class<?>> entry : map.entrySet()) {
key = entry.getKey();
}
selecedUserFilter = new UserFilter(getSelectedUserFilterValueClass, key , getSelectedUserFilterValueClass.toString(), String.valueOf(UUID.randomUUID());
}
any suggestions are appreciated
Thanks
Problems of creating objects of one interface/abstract class, based on some kind of input data (like ids, numbers, etc.) are supposed to be solved using factory method design pattern, or abstract factory pattern. Your code is a kind of factory method, and using if/else statements are perfectly fine in such situations. Here you can check an example of factory method, and abstract factory.
I'm trying to put some Strings to a HashMap, but they wont add.
My code looks like this and I can't seem to understand why they won't add. Can someone help me and give me an explanation to what I'm doing wrong?
HashMap <String, String> akro = new HashMap <String, String>();
public void lesFil() {
try {
BufferedReader br = new BufferedReader(new FileReader("akronymer.txt"));
String line;
while((line = br.readLine()) != null) {
if(!line.contains(" ")) {
continue;
}
String[] linje = line.split("\\s+", 2);
String akronym = linje[0];
String betydning = linje[1];
// System.out.println(a + " || " + b);
akro.put(akronym, betydning);
}
} catch(Exception e) {
System.out.println("Feilen som ble fanget opp: " + e);
}
}
When I'm removing "//", both akronym and betydning prints out fine.
I tried to add this method to test the HashMap but nothing prints out and the size = 0
public void skrivUt() {
for(Map.Entry <String, String> entry : akro.entrySet()) {
System.out.print("Key: " + entry.getKey());
System.out.println(", Value: " + entry.getValue());
}
System.out.println("Antall akronymer: " + akro.size());
}
Part of the file I'm reading from(txt file):
...
CS Chip Select
CS Clear to Send
CS Code Segment
C/S Client/Server
...
Remember that a Map in Java maps one key to one value. In the sample data you provide, it seems that you have multiple values ("Chip Select", "Clear to Send", "Code Segment") for one key ("CS").
You can solve this by either picking a structure other than a Map, changing what you want to store, or changing the value of the Map to a List<String>.
For example:
List<String> values = akro.get(akronym);
if(values == null) {
values = new LinkedList<String>();
akro.put(akronym, values);
}
values.add(betydning);
I have a string in the format nm=Alan&hei=72&hair=brown
I would like to split this information up, add a conversion to the first value and print the results in the format
nm Name Alan
hei Height 72
hair Hair Color brown
I've looked at various methods using the split function and hashmaps but have had no luck piecing it all together.
Any advice would be very useful to me.
Map<String, String> aliases = new HashMap<String, String>();
aliases.put("nm", "Name");
aliases.put("hei", "Height");
aliases.put("hair", "Hair Color");
String[] params = str.split("&"); // gives you string array: nm=Alan, hei=72, hair=brown
for (String p : params) {
String[] nv = p.split("=");
String name = nv[0];
String value = nv[1];
System.out.println(nv[0] + " " + aliases.get(nv[0]) + " " + nv[1]);
}
I really do not understand what you problem was...
Try something like this:
static final String DELIMETER = "&"
Map<String,String> map = ...
map.put("nm","Name");
map.put("hei","Height");
map.put("hair","Hair color");
StringBuilder builder = new StringBuilder();
String input = "nm=Alan&hei=72&hair=brown"
String[] splitted = input.split(DELIMETER);
for(Stirng str : splitted){
int index = str.indexOf("=");
String key = str.substring(0,index);
builder.append(key);
builder.append(map.get(key));
builder.append(str.substring(index));
builder.append("\n");
}
A HashMap consists of many key, value pairs. So when you use split, devise an appropriate regex (&). Once you have your string array, you can use one of the elements as the key (think about which element will make the best key). However, you may now be wondering- "how do I place the rest of elements as the values?". Perhaps you can create a new class which stores the rest of the elements and use objects of this class as values for the hashmap.
Then printing becomes easy- merely search for the value of the corresponding key. This value will be an object; use the appropriate method on this object to retrieve the elements and you should be able to print everything.
Also, remember to handle exceptions in your code. e.g. check for nulls, etc.
Another thing: your qn mentions the word "sort". I don't fully get what that means in this context...
Map<String, String> propsMap = new HashMap<String, String>();
Map<String, String> propAlias = new HashMap<String, String>();
propAlias.put("nm", "Name");
propAlias.put("hei", "Height");
propAlias.put("hair", "Hair Color");
String[] props = input.split("&");
if (props != null && props.length > 0) {
for (String prop : props) {
String[] propVal = prop.split("=");
if (propVal != null && propVal.length == 2) {
propsMap.put(propVal[0], propVal[1]);
}
}
}
for (Map.Entry tuple : propsMap.getEntrySet()) {
if (propAlias.containsKey(tuple.getKey())) {
System.out.println(tuple.getKey() + " " + propAlias.get(tuple.getKey()) + " " + tuple.getValue());
}
}
Does anyone have, or know of, a java class that I can use to manipulate query strings?
Essentially I'd like a class that I can simply give a query string to and then delete, add and modify query string KVP's.
Thanks in advance.
EDIT
In response to a comment made to this question, the query string will look something like this;
N=123+456+112&Ntt=koala&D=abc
So I'd like to pass this class the query string and say something like;
String[] N = queryStringClass.getParameter("N");
and then maybe
queryStringClass.setParameter("N", N);
and maybe queryStringClass.removeParameter("N");
Or something to that effect.
SOmething like this
public static Map<String, String> getQueryMap(String query)
{
String[] params = query.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params)
{
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}
return map;
}
To iterate the map simply:
String query = url.getQuery();
Map<String, String> map = getQueryMap(query);
Set<String> keys = map.keySet();
for (String key : keys)
{
System.out.println("Name=" + key);
System.out.println("Value=" + map.get(key));
}
You can also use Google Guava's Splitter.
String queryString = "variableA=89&variableB=100";
Map<String,String> queryParameters = Splitter
.on("&")
.withKeyValueSeparator("=")
.split(queryString);
System.out.println(queryParameters.get("variableA"));
prints out
89
This I think is a very readable alternative to parsing it yourself.
Edit: As #raulk pointed out, this solution does not account for escaped characters. However, this may not be an issue because before you URL-Decode, the query string is guaranteed to not have any escaped characters that conflict with '=' and '&'. You can use this to your advantage in the following way.
Say that you must decode the following query string:
a=%26%23%25!)%23(%40!&b=%23%24(%40)%24%40%40))%24%23%5E*%26
which is URL encoded, then you are guaranteed that the '&' and '=' are specifically used for separating pairs and key from value, respectively, at which point you can use the Guava splitter to get:
a = %26%23%25!)%23(%40!
b = %23%24(%40)%24%40%40))%24%23%5E*%26
Once you have obtained the key-value pairs, then you can URL decode them separately.
a = &#%!)#(#!
b = #$(#)$##))$#^*&
That should cover all cases.
If you are using J2EE, you can use ServletRequest.getParameterValues().
Otherwise, I don't think Java has any common classes for query string handling. Writing your own shouldn't be too hard, though there are certain tricky edge cases, such as realizing that technically the same key may appear more than once in the query string.
One implementation might look like:
import java.util.*;
import java.net.URLEncoder;
import java.net.URLDecoder;
public class QueryParams {
private static class KVP {
final String key;
final String value;
KVP (String key, String value) {
this.key = key;
this.value = value;
}
}
List<KVP> query = new ArrayList<KVP>();
public QueryParams(String queryString) {
parse(queryString);
}
public QueryParams() {
}
public void addParam(String key, String value) {
if (key == null || value == null)
throw new NullPointerException("null parameter key or value");
query.add(new KVP(key, value));
}
private void parse(String queryString) {
for (String pair : queryString.split("&")) {
int eq = pair.indexOf("=");
if (eq < 0) {
// key with no value
addParam(URLDecoder.decode(pair), "");
} else {
// key=value
String key = URLDecoder.decode(pair.substring(0, eq));
String value = URLDecoder.decode(pair.substring(eq + 1));
query.add(new KVP(key, value));
}
}
}
public String toQueryString() {
StringBuilder sb = new StringBuilder();
for (KVP kvp : query) {
if (sb.length() > 0) {
sb.append('&');
}
sb.append(URLEncoder.encode(kvp.key));
if (!kvp.value.equals("")) {
sb.append('=');
sb.append(URLEncoder.encode(kvp.value));
}
}
return sb.toString();
}
public String getParameter(String key) {
for (KVP kvp : query) {
if (kvp.key.equals(key)) {
return kvp.value;
}
}
return null;
}
public List<String> getParameterValues(String key) {
List<String> list = new LinkedList<String>();
for (KVP kvp : query) {
if (kvp.key.equals(key)) {
list.add(kvp.value);
}
}
return list;
}
public static void main(String[] args) {
QueryParams qp = new QueryParams("k1=v1&k2&k3=v3&k1=v4&k1&k5=hello+%22world");
System.out.println("getParameter:");
String[] keys = new String[] { "k1", "k2", "k3", "k5" };
for (String key : keys) {
System.out.println(key + ": " + qp.getParameter(key));
}
System.out.println("getParameters(k1): " + qp.getParameterValues("k1"));
}
}
Another way is to use apache http-components. It's a bit hacky, but at least you leverage all the parsing corner cases:
List<NameValuePair> params =
URLEncodedUtils.parse("http://example.com/?" + queryString, Charset.forName("UTF-8"));
That'll give you a List of NameValuePair objects that should be easy to work with.
You can create a util method and use regular expression to parse it. A pattern like "[;&]" should suffice.