Parse a URI String into Name-Value Collection

前端 未结 19 2455
难免孤独
难免孤独 2020-11-22 01:34

I\'ve got the URI like this:

https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_         


        
相关标签:
19条回答
  • 2020-11-22 02:09

    If you are using Spring Framework:

    public static void main(String[] args) {
        String uri = "http://my.test.com/test?param1=ab&param2=cd&param2=ef";
        MultiValueMap<String, String> parameters =
                UriComponentsBuilder.fromUriString(uri).build().getQueryParams();
        List<String> param1 = parameters.get("param1");
        List<String> param2 = parameters.get("param2");
        System.out.println("param1: " + param1.get(0));
        System.out.println("param2: " + param2.get(0) + "," + param2.get(1));
    }
    

    You will get:

    param1: ab
    param2: cd,ef
    
    0 讨论(0)
  • 2020-11-22 02:09

    use google Guava and do it in 2 lines:

    import java.util.Map;
    import com.google.common.base.Splitter;
    
    public class Parser {
        public static void main(String... args) {
            String uri = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
            String query = uri.split("\\?")[1];
            final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(query);
            System.out.println(map);
        }
    }
    

    which gives you

    {client_id=SS, response_type=code, scope=N_FULL, access_type=offline, redirect_uri=http://localhost/Callback}
    
    0 讨论(0)
  • 2020-11-22 02:10

    org.apache.http.client.utils.URLEncodedUtils

    is a well known library that can do it for you

    import org.apache.hc.client5.http.utils.URLEncodedUtils
    
    String url = "http://www.example.com/something.html?one=1&two=2&three=3&three=3a";
    
    List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), Charset.forName("UTF-8"));
    
    for (NameValuePair param : params) {
      System.out.println(param.getName() + " : " + param.getValue());
    }
    

    Outputs

    one : 1
    two : 2
    three : 3
    three : 3a
    
    0 讨论(0)
  • 2020-11-22 02:10

    A ready-to-use solution for decoding of URI query part (incl. decoding and multi parameter values)

    Comments

    I wasn't happy with the code provided by @Pr0gr4mm3r in https://stackoverflow.com/a/13592567/1211082 . The Stream-based solution does not do URLDecoding, the mutable version clumpsy.

    Thus I elaborated a solution that

    • Can decompose a URI query part into a Map<String, List<Optional<String>>>
    • Can handle multiple values for the same parameter name
    • Can represent parameters without a value properly (Optional.empty() instead of null)
    • Decodes parameter names and values correctly via URLdecode
    • Is based on Java 8 Streams
    • Is directly usable (see code including imports below)
    • Allows for proper error handling (here via turning a checked exception UnsupportedEncodingExceptioninto a runtime exception RuntimeUnsupportedEncodingException that allows interplay with stream. (Wrapping regular function into functions throwing checked exceptions is a pain. And Scala Try is not available in the Java language default.)

    Java Code

    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import java.util.*;
    import static java.util.stream.Collectors.*;
    
    public class URIParameterDecode {
        /**
         * Decode parameters in query part of a URI into a map from parameter name to its parameter values.
         * For parameters that occur multiple times each value is collected.
         * Proper decoding of the parameters is performed.
         * 
         * Example
         *   <pre>a=1&b=2&c=&a=4</pre>
         * is converted into
         *   <pre>{a=[Optional[1], Optional[4]], b=[Optional[2]], c=[Optional.empty]}</pre>
         * @param query the query part of an URI 
         * @return map of parameters names into a list of their values.
         *         
         */
        public static Map<String, List<Optional<String>>> splitQuery(String query) {
            if (query == null || query.isEmpty()) {
                return Collections.emptyMap();
            }
    
            return Arrays.stream(query.split("&"))
                        .map(p -> splitQueryParameter(p))
                        .collect(groupingBy(e -> e.get0(), // group by parameter name
                                mapping(e -> e.get1(), toList())));// keep parameter values and assemble into list
        }
    
        public static Pair<String, Optional<String>> splitQueryParameter(String parameter) {
            final String enc = "UTF-8";
            List<String> keyValue = Arrays.stream(parameter.split("="))
                    .map(e -> {
                        try {
                            return URLDecoder.decode(e, enc);
                        } catch (UnsupportedEncodingException ex) {
                            throw new RuntimeUnsupportedEncodingException(ex);
                        }
                    }).collect(toList());
    
            if (keyValue.size() == 2) {
                return new Pair(keyValue.get(0), Optional.of(keyValue.get(1)));
            } else {
                return new Pair(keyValue.get(0), Optional.empty());
            }
        }
    
        /** Runtime exception (instead of checked exception) to denote unsupported enconding */
        public static class RuntimeUnsupportedEncodingException extends RuntimeException {
            public RuntimeUnsupportedEncodingException(Throwable cause) {
                super(cause);
            }
        }
    
        /**
         * A simple pair of two elements
         * @param <U> first element
         * @param <V> second element
         */
        public static class Pair<U, V> {
            U a;
            V b;
    
            public Pair(U u, V v) {
                this.a = u;
                this.b = v;
            }
    
            public U get0() {
                return a;
            }
    
            public V get1() {
                return b;
            }
        }
    }
    

    Scala Code

    ... and for the sake of completeness I can not resist to provide the solution in Scala that dominates by brevity and beauty

    import java.net.URLDecoder
    
    object Decode {
      def main(args: Array[String]): Unit = {
        val input = "a=1&b=2&c=&a=4";
        println(separate(input))
      }
    
      def separate(input: String) : Map[String, List[Option[String]]] = {
        case class Parameter(key: String, value: Option[String])
    
        def separateParameter(parameter: String) : Parameter =
          parameter.split("=")
                   .map(e => URLDecoder.decode(e, "UTF-8")) match {
          case Array(key, value) =>  Parameter(key, Some(value))
          case Array(key) => Parameter(key, None)
        }
    
        input.split("&").toList
          .map(p => separateParameter(p))
          .groupBy(p => p.key)
          .mapValues(vs => vs.map(p => p.value))
      }
    }
    
    0 讨论(0)
  • 2020-11-22 02:11

    Using above mentioned comments and solutions, I am storing all the query parameters using Map<String, Object> where Objects either can be string or Set<String>. The solution is given below. It is recommended to use some kind of url validator to validate the url first and then call convertQueryStringToMap method.

    private static final String DEFAULT_ENCODING_SCHEME = "UTF-8";
    
    public static Map<String, Object> convertQueryStringToMap(String url) throws UnsupportedEncodingException, URISyntaxException {
        List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), DEFAULT_ENCODING_SCHEME);
        Map<String, Object> queryStringMap = new HashMap<>();
        for(NameValuePair param : params){
            queryStringMap.put(param.getName(), handleMultiValuedQueryParam(queryStringMap, param.getName(), param.getValue()));
        }
        return queryStringMap;
    }
    
    private static Object handleMultiValuedQueryParam(Map responseMap, String key, String value) {
        if (!responseMap.containsKey(key)) {
            return value.contains(",") ? new HashSet<String>(Arrays.asList(value.split(","))) : value;
        } else {
            Set<String> queryValueSet = responseMap.get(key) instanceof Set ? (Set<String>) responseMap.get(key) : new HashSet<String>();
            if (value.contains(",")) {
                queryValueSet.addAll(Arrays.asList(value.split(",")));
            } else {
                queryValueSet.add(value);
            }
            return queryValueSet;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 02:12

    Here is my solution with reduce and Optional:

    private Optional<SimpleImmutableEntry<String, String>> splitKeyValue(String text) {
        String[] v = text.split("=");
        if (v.length == 1 || v.length == 2) {
            String key = URLDecoder.decode(v[0], StandardCharsets.UTF_8);
            String value = v.length == 2 ? URLDecoder.decode(v[1], StandardCharsets.UTF_8) : null;
            return Optional.of(new SimpleImmutableEntry<String, String>(key, value));
        } else
            return Optional.empty();
    }
    
    private HashMap<String, String> parseQuery(URI uri) {
        HashMap<String, String> params = Arrays.stream(uri.getQuery()
                .split("&"))
                .map(this::splitKeyValue)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .reduce(
                    // initial value
                    new HashMap<String, String>(), 
                    // accumulator
                    (map, kv) -> {
                         map.put(kv.getKey(), kv.getValue()); 
                         return map;
                    }, 
                    // combiner
                    (a, b) -> {
                         a.putAll(b); 
                         return a;
                    });
        return params;
    }
    
    • I ignore duplicate parameters (I take the last one).
    • I use Optional<SimpleImmutableEntry<String, String>> to ignore garbage later
    • The reduction start with an empty map, then populate it on each SimpleImmutableEntry

    In case you ask, reduce requires this weird combiner in the last parameter, which is only used in parallel streams. Its goal is to merge two intermediate results (here HashMap).

    0 讨论(0)
提交回复
热议问题